diff --git a/client/client.go b/client/client.go index 4e98b1a..05b10ff 100644 --- a/client/client.go +++ b/client/client.go @@ -103,12 +103,16 @@ type AzureClient interface { GetAzureADApps(ctx context.Context, filter, search, orderBy, expand string, selectCols []string, top int32, count bool) (azure.ApplicationList, error) GetAzureADDirectoryObject(ctx context.Context, objectId string) (json.RawMessage, error) GetAzureADGroup(ctx context.Context, objectId string, selectCols []string) (*azure.Group, error) + GetAzureADGroupEligibilityScheduleInstance(ctx context.Context, objectId string, selectCols []string) (*azure.PrivilegedAccessGroupEligibilityScheduleInstance, error) + GetAzureADGroupEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string, top int32, count bool) (azure.PrivilegedAccessGroupEligibilityScheduleInstanceList, error) GetAzureADGroupOwners(ctx context.Context, objectId string, filter string, search string, orderBy string, selectCols []string, top int32, count bool) (azure.DirectoryObjectList, error) GetAzureADGroups(ctx context.Context, filter, search, orderBy, expand string, selectCols []string, top int32, count bool) (azure.GroupList, error) GetAzureADOrganization(ctx context.Context, selectCols []string) (*azure.Organization, error) GetAzureADRole(ctx context.Context, roleId string, selectCols []string) (*azure.Role, error) GetAzureADRoleAssignment(ctx context.Context, objectId string, selectCols []string) (*azure.UnifiedRoleAssignment, error) GetAzureADRoleAssignments(ctx context.Context, filter, search, orderBy, expand string, selectCols []string, top int32, count bool) (azure.UnifiedRoleAssignmentList, error) + GetAzureADRoleEligibilityScheduleInstance(ctx context.Context, objectId string, selectCols []string) (*azure.UnifiedRoleEligibilityScheduleInstance, error) + GetAzureADRoleEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string, top int32, count bool) (azure.UnifiedRoleEligibilityScheduleInstanceList, error) GetAzureADRoles(ctx context.Context, filter, expand string) (azure.RoleList, error) GetAzureADServicePrincipal(ctx context.Context, objectId string, selectCols []string) (*azure.ServicePrincipal, error) GetAzureADServicePrincipalOwners(ctx context.Context, objectId string, filter string, search string, orderBy string, selectCols []string, top int32, count bool) (azure.DirectoryObjectList, error) @@ -138,7 +142,9 @@ type AzureClient interface { ListAzureADGroupMembers(ctx context.Context, objectId string, filter, search, orderBy string, selectCols []string) <-chan azure.MemberObjectResult ListAzureADGroupOwners(ctx context.Context, objectId string, filter, search, orderBy string, selectCols []string) <-chan azure.GroupOwnerResult ListAzureADGroups(ctx context.Context, filter, search, orderBy, expand string, selectCols []string) <-chan azure.GroupResult + ListAzureADGroupEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string) <-chan azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult ListAzureADRoleAssignments(ctx context.Context, filter, search, orderBy, expand string, selectCols []string) <-chan azure.UnifiedRoleAssignmentResult + ListAzureADRoleEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string) <-chan azure.UnifiedRoleEligibilityScheduleInstanceResult ListAzureADRoles(ctx context.Context, filter, expand string) <-chan azure.RoleResult ListAzureADServicePrincipalOwners(ctx context.Context, objectId string, filter, search, orderBy string, selectCols []string) <-chan azure.ServicePrincipalOwnerResult ListAzureADServicePrincipals(ctx context.Context, filter, search, orderBy, expand string, selectCols []string) <-chan azure.ServicePrincipalResult diff --git a/client/group_eligibility_schedule_instances.go b/client/group_eligibility_schedule_instances.go new file mode 100644 index 0000000..31b3ca8 --- /dev/null +++ b/client/group_eligibility_schedule_instances.go @@ -0,0 +1,116 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package client + +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/bloodhoundad/azurehound/v2/client/query" + "github.com/bloodhoundad/azurehound/v2/client/rest" + "github.com/bloodhoundad/azurehound/v2/constants" + "github.com/bloodhoundad/azurehound/v2/models/azure" +) + +func (s *azureClient) GetAzureADGroupEligibilityScheduleInstance(ctx context.Context, objectId string, selectCols []string) (*azure.PrivilegedAccessGroupEligibilityScheduleInstance, error) { + var ( + path = fmt.Sprintf("/%s/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances/%s", constants.GraphApiBetaVersion, objectId) + params = query.Params{Select: selectCols}.AsMap() + response azure.PrivilegedAccessGroupEligibilityScheduleInstance + ) + if res, err := s.msgraph.Get(ctx, path, params, nil); err != nil { + return nil, err + } else if err := rest.Decode(res.Body, &response); err != nil { + return nil, err + } else { + return &response, nil + } +} + +func (s *azureClient) GetAzureADGroupEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string, top int32, count bool) (azure.PrivilegedAccessGroupEligibilityScheduleInstanceList, error) { + var ( + path = fmt.Sprintf("/%s/identityGovernance/privilegedAccess/group/eligibilityScheduleInstances", constants.GraphApiBetaVersion) + params = query.Params{Filter: filter, Search: search, OrderBy: orderBy, Select: selectCols, Top: top, Count: count, Expand: expand} + headers map[string]string + response azure.PrivilegedAccessGroupEligibilityScheduleInstanceList + ) + count = count || search != "" || (filter != "" && orderBy != "") || strings.Contains(filter, "endsWith") + if count { + headers = make(map[string]string) + headers["ConsistencyLevel"] = "eventual" + } + if res, err := s.msgraph.Get(ctx, path, params.AsMap(), headers); err != nil { + return response, err + } else if err := rest.Decode(res.Body, &response); err != nil { + return response, err + } else { + return response, nil + } +} + +func (s *azureClient) ListAzureADGroupEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string) <-chan azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult { + out := make(chan azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult) + + go func() { + defer close(out) + + var ( + errResult = azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult{} + nextLink string + ) + + if list, err := s.GetAzureADGroupEligibilityScheduleInstances(ctx, filter, search, orderBy, expand, selectCols, 999, false); err != nil { + errResult.Error = err + out <- errResult + } else { + for _, u := range list.Value { + out <- azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult{Ok: u} + } + + nextLink = list.NextLink + for nextLink != "" { + var list azure.PrivilegedAccessGroupEligibilityScheduleInstanceList + if url, err := url.Parse(nextLink); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else if req, err := rest.NewRequest(ctx, "GET", url, nil, nil, nil); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else if res, err := s.msgraph.Send(req); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else if err := rest.Decode(res.Body, &list); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else { + for _, u := range list.Value { + out <- azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult{Ok: u} + } + nextLink = list.NextLink + } + } + } + }() + return out +} diff --git a/client/mocks/client.go b/client/mocks/client.go index 63bb9b0..2b08a2b 100644 --- a/client/mocks/client.go +++ b/client/mocks/client.go @@ -96,6 +96,36 @@ func (mr *MockAzureClientMockRecorder) GetAzureADGroup(arg0, arg1, arg2 interfac return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAzureADGroup", reflect.TypeOf((*MockAzureClient)(nil).GetAzureADGroup), arg0, arg1, arg2) } +// GetAzureADGroupEligibilityScheduleInstance mocks base method. +func (m *MockAzureClient) GetAzureADGroupEligibilityScheduleInstance(arg0 context.Context, arg1 string, arg2 []string) (*azure.PrivilegedAccessGroupEligibilityScheduleInstance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAzureADGroupEligibilityScheduleInstance", arg0, arg1, arg2) + ret0, _ := ret[0].(*azure.PrivilegedAccessGroupEligibilityScheduleInstance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAzureADGroupEligibilityScheduleInstance indicates an expected call of GetAzureADGroupEligibilityScheduleInstance. +func (mr *MockAzureClientMockRecorder) GetAzureADGroupEligibilityScheduleInstance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAzureADGroupEligibilityScheduleInstance", reflect.TypeOf((*MockAzureClient)(nil).GetAzureADGroupEligibilityScheduleInstance), arg0, arg1, arg2) +} + +// GetAzureADGroupEligibilityScheduleInstances mocks base method. +func (m *MockAzureClient) GetAzureADGroupEligibilityScheduleInstances(arg0 context.Context, arg1, arg2, arg3, arg4 string, arg5 []string, arg6 int32, arg7 bool) (azure.PrivilegedAccessGroupEligibilityScheduleInstanceList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAzureADGroupEligibilityScheduleInstances", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + ret0, _ := ret[0].(azure.PrivilegedAccessGroupEligibilityScheduleInstanceList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAzureADGroupEligibilityScheduleInstances indicates an expected call of GetAzureADGroupEligibilityScheduleInstances. +func (mr *MockAzureClientMockRecorder) GetAzureADGroupEligibilityScheduleInstances(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAzureADGroupEligibilityScheduleInstances", reflect.TypeOf((*MockAzureClient)(nil).GetAzureADGroupEligibilityScheduleInstances), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) +} + // GetAzureADGroupOwners mocks base method. func (m *MockAzureClient) GetAzureADGroupOwners(arg0 context.Context, arg1, arg2, arg3, arg4 string, arg5 []string, arg6 int32, arg7 bool) (azure.DirectoryObjectList, error) { m.ctrl.T.Helper() @@ -186,6 +216,36 @@ func (mr *MockAzureClientMockRecorder) GetAzureADRoleAssignments(arg0, arg1, arg return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAzureADRoleAssignments", reflect.TypeOf((*MockAzureClient)(nil).GetAzureADRoleAssignments), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) } +// GetAzureADRoleEligibilityScheduleInstance mocks base method. +func (m *MockAzureClient) GetAzureADRoleEligibilityScheduleInstance(arg0 context.Context, arg1 string, arg2 []string) (*azure.UnifiedRoleEligibilityScheduleInstance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAzureADRoleEligibilityScheduleInstance", arg0, arg1, arg2) + ret0, _ := ret[0].(*azure.UnifiedRoleEligibilityScheduleInstance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAzureADRoleEligibilityScheduleInstance indicates an expected call of GetAzureADRoleEligibilityScheduleInstance. +func (mr *MockAzureClientMockRecorder) GetAzureADRoleEligibilityScheduleInstance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAzureADRoleEligibilityScheduleInstance", reflect.TypeOf((*MockAzureClient)(nil).GetAzureADRoleEligibilityScheduleInstance), arg0, arg1, arg2) +} + +// GetAzureADRoleEligibilityScheduleInstances mocks base method. +func (m *MockAzureClient) GetAzureADRoleEligibilityScheduleInstances(arg0 context.Context, arg1, arg2, arg3, arg4 string, arg5 []string, arg6 int32, arg7 bool) (azure.UnifiedRoleEligibilityScheduleInstanceList, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAzureADRoleEligibilityScheduleInstances", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) + ret0, _ := ret[0].(azure.UnifiedRoleEligibilityScheduleInstanceList) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetAzureADRoleEligibilityScheduleInstances indicates an expected call of GetAzureADRoleEligibilityScheduleInstances. +func (mr *MockAzureClientMockRecorder) GetAzureADRoleEligibilityScheduleInstances(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAzureADRoleEligibilityScheduleInstances", reflect.TypeOf((*MockAzureClient)(nil).GetAzureADRoleEligibilityScheduleInstances), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) +} + // GetAzureADRoles mocks base method. func (m *MockAzureClient) GetAzureADRoles(arg0 context.Context, arg1, arg2 string) (azure.RoleList, error) { m.ctrl.T.Helper() @@ -587,6 +647,20 @@ func (mr *MockAzureClientMockRecorder) ListAzureADApps(arg0, arg1, arg2, arg3, a return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAzureADApps", reflect.TypeOf((*MockAzureClient)(nil).ListAzureADApps), arg0, arg1, arg2, arg3, arg4, arg5) } +// ListAzureADGroupEligibilityScheduleInstances mocks base method. +func (m *MockAzureClient) ListAzureADGroupEligibilityScheduleInstances(arg0 context.Context, arg1, arg2, arg3, arg4 string, arg5 []string) <-chan azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAzureADGroupEligibilityScheduleInstances", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(<-chan azure.PrivilegedAccessGroupEligibilityScheduleInstanceResult) + return ret0 +} + +// ListAzureADGroupEligibilityScheduleInstances indicates an expected call of ListAzureADGroupEligibilityScheduleInstances. +func (mr *MockAzureClientMockRecorder) ListAzureADGroupEligibilityScheduleInstances(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAzureADGroupEligibilityScheduleInstances", reflect.TypeOf((*MockAzureClient)(nil).ListAzureADGroupEligibilityScheduleInstances), arg0, arg1, arg2, arg3, arg4, arg5) +} + // ListAzureADGroupMembers mocks base method. func (m *MockAzureClient) ListAzureADGroupMembers(arg0 context.Context, arg1, arg2, arg3, arg4 string, arg5 []string) <-chan azure.MemberObjectResult { m.ctrl.T.Helper() @@ -643,6 +717,20 @@ func (mr *MockAzureClientMockRecorder) ListAzureADRoleAssignments(arg0, arg1, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAzureADRoleAssignments", reflect.TypeOf((*MockAzureClient)(nil).ListAzureADRoleAssignments), arg0, arg1, arg2, arg3, arg4, arg5) } +// ListAzureADRoleEligibilityScheduleInstances mocks base method. +func (m *MockAzureClient) ListAzureADRoleEligibilityScheduleInstances(arg0 context.Context, arg1, arg2, arg3, arg4 string, arg5 []string) <-chan azure.UnifiedRoleEligibilityScheduleInstanceResult { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAzureADRoleEligibilityScheduleInstances", arg0, arg1, arg2, arg3, arg4, arg5) + ret0, _ := ret[0].(<-chan azure.UnifiedRoleEligibilityScheduleInstanceResult) + return ret0 +} + +// ListAzureADRoleEligibilityScheduleInstances indicates an expected call of ListAzureADRoleEligibilityScheduleInstances. +func (mr *MockAzureClientMockRecorder) ListAzureADRoleEligibilityScheduleInstances(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAzureADRoleEligibilityScheduleInstances", reflect.TypeOf((*MockAzureClient)(nil).ListAzureADRoleEligibilityScheduleInstances), arg0, arg1, arg2, arg3, arg4, arg5) +} + // ListAzureADRoles mocks base method. func (m *MockAzureClient) ListAzureADRoles(arg0 context.Context, arg1, arg2 string) <-chan azure.RoleResult { m.ctrl.T.Helper() diff --git a/client/role_eligibility_schedule_instances.go b/client/role_eligibility_schedule_instances.go new file mode 100644 index 0000000..0397c8a --- /dev/null +++ b/client/role_eligibility_schedule_instances.go @@ -0,0 +1,116 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package client + +import ( + "context" + "fmt" + "net/url" + "strings" + + "github.com/bloodhoundad/azurehound/v2/client/query" + "github.com/bloodhoundad/azurehound/v2/client/rest" + "github.com/bloodhoundad/azurehound/v2/constants" + "github.com/bloodhoundad/azurehound/v2/models/azure" +) + +func (s *azureClient) GetAzureADRoleEligibilityScheduleInstance(ctx context.Context, objectId string, selectCols []string) (*azure.UnifiedRoleEligibilityScheduleInstance, error) { + var ( + path = fmt.Sprintf("/%s/roleManagement/directory/RoleEligibilityScheduleInstances/%s", constants.GraphApiVersion, objectId) + params = query.Params{Select: selectCols}.AsMap() + response azure.UnifiedRoleEligibilityScheduleInstance + ) + if res, err := s.msgraph.Get(ctx, path, params, nil); err != nil { + return nil, err + } else if err := rest.Decode(res.Body, &response); err != nil { + return nil, err + } else { + return &response, nil + } +} + +func (s *azureClient) GetAzureADRoleEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string, top int32, count bool) (azure.UnifiedRoleEligibilityScheduleInstanceList, error) { + var ( + path = fmt.Sprintf("/%s/roleManagement/directory/RoleEligibilityScheduleInstances", constants.GraphApiVersion) + params = query.Params{Filter: filter, Search: search, OrderBy: orderBy, Select: selectCols, Top: top, Count: count, Expand: expand} + headers map[string]string + response azure.UnifiedRoleEligibilityScheduleInstanceList + ) + count = count || search != "" || (filter != "" && orderBy != "") || strings.Contains(filter, "endsWith") + if count { + headers = make(map[string]string) + headers["ConsistencyLevel"] = "eventual" + } + if res, err := s.msgraph.Get(ctx, path, params.AsMap(), headers); err != nil { + return response, err + } else if err := rest.Decode(res.Body, &response); err != nil { + return response, err + } else { + return response, nil + } +} + +func (s *azureClient) ListAzureADRoleEligibilityScheduleInstances(ctx context.Context, filter, search, orderBy, expand string, selectCols []string) <-chan azure.UnifiedRoleEligibilityScheduleInstanceResult { + out := make(chan azure.UnifiedRoleEligibilityScheduleInstanceResult) + + go func() { + defer close(out) + + var ( + errResult = azure.UnifiedRoleEligibilityScheduleInstanceResult{} + nextLink string + ) + + if list, err := s.GetAzureADRoleEligibilityScheduleInstances(ctx, filter, search, orderBy, expand, selectCols, 999, false); err != nil { + errResult.Error = err + out <- errResult + } else { + for _, u := range list.Value { + out <- azure.UnifiedRoleEligibilityScheduleInstanceResult{Ok: u} + } + + nextLink = list.NextLink + for nextLink != "" { + var list azure.UnifiedRoleEligibilityScheduleInstanceList + if url, err := url.Parse(nextLink); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else if req, err := rest.NewRequest(ctx, "GET", url, nil, nil, nil); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else if res, err := s.msgraph.Send(req); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else if err := rest.Decode(res.Body, &list); err != nil { + errResult.Error = err + out <- errResult + nextLink = "" + } else { + for _, u := range list.Value { + out <- azure.UnifiedRoleEligibilityScheduleInstanceResult{Ok: u} + } + nextLink = list.NextLink + } + } + } + }() + return out +} diff --git a/cmd/list-azure-ad.go b/cmd/list-azure-ad.go index c53fcaa..3a22904 100644 --- a/cmd/list-azure-ad.go +++ b/cmd/list-azure-ad.go @@ -67,9 +67,11 @@ func listAllAD(ctx context.Context, client client.AzureClient) <-chan interface{ groups = make(chan interface{}) groups2 = make(chan interface{}) groups3 = make(chan interface{}) + groups4 = make(chan interface{}) roles = make(chan interface{}) roles2 = make(chan interface{}) + roles3 = make(chan interface{}) servicePrincipals = make(chan interface{}) servicePrincipals2 = make(chan interface{}) @@ -88,10 +90,13 @@ func listAllAD(ctx context.Context, client client.AzureClient) <-chan interface{ deviceOwners := listDeviceOwners(ctx, client, devices2) // Enumerate Groups, GroupOwners and GroupMembers - pipeline.Tee(ctx.Done(), listGroups(ctx, client), groups, groups2, groups3) + pipeline.Tee(ctx.Done(), listGroups(ctx, client), groups, groups2, groups3, groups4) groupOwners := listGroupOwners(ctx, client, groups2) groupMembers := listGroupMembers(ctx, client, groups3) + // Enumerate Groups Eligibility Schedule Instances + groupEligibilityScheduleInstances := listGroupEligibilityScheduleInstances(ctx, client, groups4) + // Enumerate ServicePrincipals and ServicePrincipalOwners pipeline.Tee(ctx.Done(), listServicePrincipals(ctx, client), servicePrincipals, servicePrincipals2, servicePrincipals3) servicePrincipalOwners := listServicePrincipalOwners(ctx, client, servicePrincipals2) @@ -103,9 +108,12 @@ func listAllAD(ctx context.Context, client client.AzureClient) <-chan interface{ users := listUsers(ctx, client) // Enumerate Roles and RoleAssignments - pipeline.Tee(ctx.Done(), listRoles(ctx, client), roles, roles2) + pipeline.Tee(ctx.Done(), listRoles(ctx, client), roles, roles2, roles3) roleAssignments := listRoleAssignments(ctx, client, roles2) + // Enumerate Roles Eligibility Schedule Instances + roleEligibilityScheduleInstances := listRoleEligibilityScheduleInstances(ctx, client, roles3) + // Enumerate AppRoleAssignments appRoleAssignments := listAppRoleAssignments(ctx, client, servicePrincipals3) @@ -115,9 +123,11 @@ func listAllAD(ctx context.Context, client client.AzureClient) <-chan interface{ apps, deviceOwners, devices, + groupEligibilityScheduleInstances, groupMembers, groupOwners, groups, + roleEligibilityScheduleInstances, roleAssignments, roles, servicePrincipalOwners, diff --git a/cmd/list-group-eligibility-schedule-instances.go b/cmd/list-group-eligibility-schedule-instances.go new file mode 100644 index 0000000..565ecf9 --- /dev/null +++ b/cmd/list-group-eligibility-schedule-instances.go @@ -0,0 +1,121 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package cmd + +import ( + "context" + "fmt" + "os" + "os/signal" + "sync" + "time" + + "github.com/bloodhoundad/azurehound/v2/client" + "github.com/bloodhoundad/azurehound/v2/enums" + "github.com/bloodhoundad/azurehound/v2/models" + "github.com/bloodhoundad/azurehound/v2/pipeline" + "github.com/spf13/cobra" +) + +func init() { + listRootCmd.AddCommand(listGroupEligibilityScheduleInstancesCmd) +} + +var listGroupEligibilityScheduleInstancesCmd = &cobra.Command{ + Use: "group-eligibility-schedule-instances", + Long: "Lists Azure Active Directory Group Eligibility Instances", + Run: listGroupEligibilityScheduleInstancesCmdImpl, + SilenceUsage: true, +} + +func listGroupEligibilityScheduleInstancesCmdImpl(cmd *cobra.Command, args []string) { + ctx, stop := signal.NotifyContext(cmd.Context(), os.Interrupt, os.Kill) + defer gracefulShutdown(stop) + + log.V(1).Info("testing connections") + azClient := connectAndCreateClient() + log.Info("collecting azure active directory group eligibility instances...") + start := time.Now() + groups := listGroups(ctx, azClient) + stream := listGroupEligibilityScheduleInstances(ctx, azClient, groups) + outputStream(ctx, stream) + duration := time.Since(start) + log.Info("collection completed", "duration", duration.String()) +} + +func listGroupEligibilityScheduleInstances(ctx context.Context, client client.AzureClient, groups <-chan interface{}) <-chan interface{} { + var ( + out = make(chan interface{}) + ids = make(chan string) + streams = pipeline.Demux(ctx.Done(), ids, 25) + wg sync.WaitGroup + ) + + go func() { + defer close(ids) + + for result := range pipeline.OrDone(ctx.Done(), groups) { + if group, ok := result.(AzureWrapper).Data.(models.Group); !ok { + log.Error(fmt.Errorf("failed type assertion"), "unable to continue enumerating group eligibility schedule instances", "result", result) + return + } else { + ids <- group.Id + } + } + }() + + wg.Add(len(streams)) + for i := range streams { + stream := streams[i] + go func() { + defer wg.Done() + for id := range stream { + var ( + groupEligibilityScheduleInstances = models.GroupEligibilityScheduleInstances{ + GroupId: id, + TenantId: client.TenantInfo().TenantId, + } + count = 0 + filter = fmt.Sprintf("groupId eq '%s'", id) + ) + for item := range client.ListAzureADGroupEligibilityScheduleInstances(ctx, filter, "", "", "", nil) { + if item.Error != nil { + log.Error(item.Error, "unable to continue processing group eligibility schedule instances for this group", "groupId", id) + } else { + log.V(2).Info("found group eligibility schedule instance", "groupEligibilityScheduleInstance", item) + count++ + groupEligibilityScheduleInstances.GroupEligibilityScheduleInstances = append(groupEligibilityScheduleInstances.GroupEligibilityScheduleInstances, item.Ok) + } + } + out <- AzureWrapper{ + Kind: enums.KindAZGroupEligibilityScheduleInstance, + Data: groupEligibilityScheduleInstances, + } + log.V(1).Info("finished listing group eligibility schedule instances", "groupId", id, "count", count) + } + }() + } + + go func() { + wg.Wait() + close(out) + log.Info("finished listing all group eligibility schedule instances") + }() + + return out +} diff --git a/cmd/list-role-eligibility-schedule-instances.go b/cmd/list-role-eligibility-schedule-instances.go new file mode 100644 index 0000000..1169f03 --- /dev/null +++ b/cmd/list-role-eligibility-schedule-instances.go @@ -0,0 +1,121 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package cmd + +import ( + "context" + "fmt" + "os" + "os/signal" + "sync" + "time" + + "github.com/bloodhoundad/azurehound/v2/client" + "github.com/bloodhoundad/azurehound/v2/enums" + "github.com/bloodhoundad/azurehound/v2/models" + "github.com/bloodhoundad/azurehound/v2/pipeline" + "github.com/spf13/cobra" +) + +func init() { + listRootCmd.AddCommand(listRoleEligibilityScheduleInstancesCmd) +} + +var listRoleEligibilityScheduleInstancesCmd = &cobra.Command{ + Use: "role-eligibility-schedule-instances", + Long: "Lists Azure Active Directory Role Eligibility Instances", + Run: listRoleEligibilityScheduleInstancesCmdImpl, + SilenceUsage: true, +} + +func listRoleEligibilityScheduleInstancesCmdImpl(cmd *cobra.Command, args []string) { + ctx, stop := signal.NotifyContext(cmd.Context(), os.Interrupt, os.Kill) + defer gracefulShutdown(stop) + + log.V(1).Info("testing connections") + azClient := connectAndCreateClient() + log.Info("collecting azure active directory role eligibility instances...") + start := time.Now() + roles := listRoles(ctx, azClient) + stream := listRoleEligibilityScheduleInstances(ctx, azClient, roles) + outputStream(ctx, stream) + duration := time.Since(start) + log.Info("collection completed", "duration", duration.String()) +} + +func listRoleEligibilityScheduleInstances(ctx context.Context, client client.AzureClient, roles <-chan interface{}) <-chan interface{} { + var ( + out = make(chan interface{}) + ids = make(chan string) + streams = pipeline.Demux(ctx.Done(), ids, 25) + wg sync.WaitGroup + ) + + go func() { + defer close(ids) + + for result := range pipeline.OrDone(ctx.Done(), roles) { + if role, ok := result.(AzureWrapper).Data.(models.Role); !ok { + log.Error(fmt.Errorf("failed type assertion"), "unable to continue enumerating role eligibility schedule instances", "result", result) + return + } else { + ids <- role.Id + } + } + }() + + wg.Add(len(streams)) + for i := range streams { + stream := streams[i] + go func() { + defer wg.Done() + for id := range stream { + var ( + roleEligibilityScheduleInstances = models.RoleEligibilityScheduleInstances{ + RoleDefinitionId: id, + TenantId: client.TenantInfo().TenantId, + } + count = 0 + filter = fmt.Sprintf("roleDefinitionId eq '%s'", id) + ) + for item := range client.ListAzureADRoleEligibilityScheduleInstances(ctx, filter, "", "", "", nil) { + if item.Error != nil { + log.Error(item.Error, "unable to continue processing role eligibility schedule instances for this role", "roleDefinitionId", id) + } else { + log.V(2).Info("found role eligibility schedule instance", "roleEligibilityScheduleInstance", item) + count++ + roleEligibilityScheduleInstances.RoleEligibilityScheduleInstances = append(roleEligibilityScheduleInstances.RoleEligibilityScheduleInstances, item.Ok) + } + } + out <- AzureWrapper{ + Kind: enums.KindAZRoleEligibilityScheduleInstance, + Data: roleEligibilityScheduleInstances, + } + log.V(1).Info("finished listing role eligibility schedule instances", "roleDefinitionId", id, "count", count) + } + }() + } + + go func() { + wg.Wait() + close(out) + log.Info("finished listing all role eligibility schedule instances") + }() + + return out +} diff --git a/enums/kind.go b/enums/kind.go index 9d3fbd9..bf6859d 100644 --- a/enums/kind.go +++ b/enums/kind.go @@ -20,64 +20,66 @@ package enums type Kind string const ( - KindAZApp Kind = "AZApp" - KindAZAppMember Kind = "AZAppMember" - KindAZAppOwner Kind = "AZAppOwner" - KindAZDevice Kind = "AZDevice" - KindAZDeviceOwner Kind = "AZDeviceOwner" - KindAZGroup Kind = "AZGroup" - KindAZGroupMember Kind = "AZGroupMember" - KindAZGroupOwner Kind = "AZGroupOwner" - KindAZKeyVault Kind = "AZKeyVault" - KindAZKeyVaultAccessPolicy Kind = "AZKeyVaultAccessPolicy" - KindAZKeyVaultContributor Kind = "AZKeyVaultContributor" - KindAZKeyVaultKVContributor Kind = "AZKeyVaultKVContributor" - KindAZKeyVaultOwner Kind = "AZKeyVaultOwner" - KindAZKeyVaultRoleAssignment Kind = "AZKeyVaultRoleAssignment" - KindAZKeyVaultUserAccessAdmin Kind = "AZKeyVaultUserAccessAdmin" - KindAZManagementGroup Kind = "AZManagementGroup" - KindAZManagementGroupRoleAssignment Kind = "AZManagementGroupRoleAssignment" - KindAZManagementGroupOwner Kind = "AZManagementGroupOwner" - KindAZManagementGroupDescendant Kind = "AZManagementGroupDescendant" - KindAZManagementGroupUserAccessAdmin Kind = "AZManagementGroupUserAccessAdmin" - KindAZResourceGroup Kind = "AZResourceGroup" - KindAZResourceGroupRoleAssignment Kind = "AZResourceGroupRoleAssignment" - KindAZResourceGroupOwner Kind = "AZResourceGroupOwner" - KindAZResourceGroupUserAccessAdmin Kind = "AZResourceGroupUserAccessAdmin" - KindAZRole Kind = "AZRole" - KindAZRoleAssignment Kind = "AZRoleAssignment" - KindAZServicePrincipal Kind = "AZServicePrincipal" - KindAZServicePrincipalOwner Kind = "AZServicePrincipalOwner" - KindAZSubscription Kind = "AZSubscription" - KindAZSubscriptionRoleAssignment Kind = "AZSubscriptionRoleAssignment" - KindAZSubscriptionOwner Kind = "AZSubscriptionOwner" - KindAZSubscriptionUserAccessAdmin Kind = "AZSubscriptionUserAccessAdmin" - KindAZTenant Kind = "AZTenant" - KindAZUser Kind = "AZUser" - KindAZVM Kind = "AZVM" - KindAZVMAdminLogin Kind = "AZVMAdminLogin" - KindAZVMAvereContributor Kind = "AZVMAvereContributor" - KindAZVMContributor Kind = "AZVMContributor" - KindAZVMOwner Kind = "AZVMOwner" - KindAZVMRoleAssignment Kind = "AZVMRoleAssignment" - KindAZVMUserAccessAdmin Kind = "AZVMUserAccessAdmin" - KindAZVMVMContributor Kind = "AZVMVMContributor" - KindAZAppRoleAssignment Kind = "AZAppRoleAssignment" - KindAZStorageAccount Kind = "AZStorageAccount" - KindAZStorageAccountRoleAssignment Kind = "AZStorageAccountRoleAssignment" - KindAZStorageContainer Kind = "AZStorageContainer" - KindAZAutomationAccount Kind = "AZAutomationAccount" - KindAZAutomationAccountRoleAssignment Kind = "AZAutomationAccountRoleAssignment" - KindAZLogicApp Kind = "AZLogicApp" - KindAZLogicAppRoleAssignment Kind = "AZLogicAppRoleAssignment" - KindAZFunctionApp Kind = "AZFunctionApp" - KindAZFunctionAppRoleAssignment Kind = "AZFunctionAppRoleAssignment" - KindAZContainerRegistry Kind = "AZContainerRegistry" - KindAZContainerRegistryRoleAssignment Kind = "AZContainerRegistryRoleAssignment" - KindAZWebApp Kind = "AZWebApp" - KindAZWebAppRoleAssignment Kind = "AZWebAppRoleAssignment" - KindAZManagedCluster Kind = "AZManagedCluster" - KindAZManagedClusterRoleAssignment Kind = "AZManagedClusterRoleAssignment" - KindAZVMScaleSet Kind = "AZVMScaleSet" - KindAZVMScaleSetRoleAssignment Kind = "AZVMScaleSetRoleAssignment" + KindAZApp Kind = "AZApp" + KindAZAppMember Kind = "AZAppMember" + KindAZAppOwner Kind = "AZAppOwner" + KindAZDevice Kind = "AZDevice" + KindAZDeviceOwner Kind = "AZDeviceOwner" + KindAZGroup Kind = "AZGroup" + KindAZGroupEligibilityScheduleInstance Kind = "AZGroupEligibilityScheduleInstance" + KindAZGroupMember Kind = "AZGroupMember" + KindAZGroupOwner Kind = "AZGroupOwner" + KindAZKeyVault Kind = "AZKeyVault" + KindAZKeyVaultAccessPolicy Kind = "AZKeyVaultAccessPolicy" + KindAZKeyVaultContributor Kind = "AZKeyVaultContributor" + KindAZKeyVaultKVContributor Kind = "AZKeyVaultKVContributor" + KindAZKeyVaultOwner Kind = "AZKeyVaultOwner" + KindAZKeyVaultRoleAssignment Kind = "AZKeyVaultRoleAssignment" + KindAZKeyVaultUserAccessAdmin Kind = "AZKeyVaultUserAccessAdmin" + KindAZManagementGroup Kind = "AZManagementGroup" + KindAZManagementGroupRoleAssignment Kind = "AZManagementGroupRoleAssignment" + KindAZManagementGroupOwner Kind = "AZManagementGroupOwner" + KindAZManagementGroupDescendant Kind = "AZManagementGroupDescendant" + KindAZManagementGroupUserAccessAdmin Kind = "AZManagementGroupUserAccessAdmin" + KindAZResourceGroup Kind = "AZResourceGroup" + KindAZResourceGroupRoleAssignment Kind = "AZResourceGroupRoleAssignment" + KindAZResourceGroupOwner Kind = "AZResourceGroupOwner" + KindAZResourceGroupUserAccessAdmin Kind = "AZResourceGroupUserAccessAdmin" + KindAZRole Kind = "AZRole" + KindAZRoleAssignment Kind = "AZRoleAssignment" + KindAZRoleEligibilityScheduleInstance Kind = "AZRoleEligibilityScheduleInstance" + KindAZServicePrincipal Kind = "AZServicePrincipal" + KindAZServicePrincipalOwner Kind = "AZServicePrincipalOwner" + KindAZSubscription Kind = "AZSubscription" + KindAZSubscriptionRoleAssignment Kind = "AZSubscriptionRoleAssignment" + KindAZSubscriptionOwner Kind = "AZSubscriptionOwner" + KindAZSubscriptionUserAccessAdmin Kind = "AZSubscriptionUserAccessAdmin" + KindAZTenant Kind = "AZTenant" + KindAZUser Kind = "AZUser" + KindAZVM Kind = "AZVM" + KindAZVMAdminLogin Kind = "AZVMAdminLogin" + KindAZVMAvereContributor Kind = "AZVMAvereContributor" + KindAZVMContributor Kind = "AZVMContributor" + KindAZVMOwner Kind = "AZVMOwner" + KindAZVMRoleAssignment Kind = "AZVMRoleAssignment" + KindAZVMUserAccessAdmin Kind = "AZVMUserAccessAdmin" + KindAZVMVMContributor Kind = "AZVMVMContributor" + KindAZAppRoleAssignment Kind = "AZAppRoleAssignment" + KindAZStorageAccount Kind = "AZStorageAccount" + KindAZStorageAccountRoleAssignment Kind = "AZStorageAccountRoleAssignment" + KindAZStorageContainer Kind = "AZStorageContainer" + KindAZAutomationAccount Kind = "AZAutomationAccount" + KindAZAutomationAccountRoleAssignment Kind = "AZAutomationAccountRoleAssignment" + KindAZLogicApp Kind = "AZLogicApp" + KindAZLogicAppRoleAssignment Kind = "AZLogicAppRoleAssignment" + KindAZFunctionApp Kind = "AZFunctionApp" + KindAZFunctionAppRoleAssignment Kind = "AZFunctionAppRoleAssignment" + KindAZContainerRegistry Kind = "AZContainerRegistry" + KindAZContainerRegistryRoleAssignment Kind = "AZContainerRegistryRoleAssignment" + KindAZWebApp Kind = "AZWebApp" + KindAZWebAppRoleAssignment Kind = "AZWebAppRoleAssignment" + KindAZManagedCluster Kind = "AZManagedCluster" + KindAZManagedClusterRoleAssignment Kind = "AZManagedClusterRoleAssignment" + KindAZVMScaleSet Kind = "AZVMScaleSet" + KindAZVMScaleSetRoleAssignment Kind = "AZVMScaleSetRoleAssignment" ) diff --git a/go.mod b/go.mod index 5c9ca77..f01a218 100644 --- a/go.mod +++ b/go.mod @@ -31,7 +31,10 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.2.0 // indirect golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/mod v0.5.0 // indirect golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 9f9a43d..160bec4 100644 --- a/go.sum +++ b/go.sum @@ -422,6 +422,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -633,10 +634,12 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= diff --git a/models/azure/privileged_access_group_eligibility_schedule_instance.go b/models/azure/privileged_access_group_eligibility_schedule_instance.go new file mode 100644 index 0000000..3741f24 --- /dev/null +++ b/models/azure/privileged_access_group_eligibility_schedule_instance.go @@ -0,0 +1,57 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package azure + +type PrivilegedAccessGroupEligibilityScheduleInstance struct { + Entity + + // When this instance starts. + StartDateTime string `json:"startDateTime"` + + // When the schedule instance ends. + EndDateTime string `json:"endDateTime"` + + // The identifier of the principal whose membership or ownership eligibility to the group is managed through PIM for groups. + PrincipalId string `json:"principalId"` + + // The identifier of the membership or ownership eligibility relationship to the group. + // The possible values are: owner, member. + AccessId string `json:"accessId"` + + // The identifier of the group representing the scope of the membership or ownership eligibility through PIM for groups. + GroupId string `json:"groupId"` + + // Indicates whether the assignment is derived from a group assignment. It can further imply whether the calling + // principal can manage the assignment schedule. + // The possible values are: direct, group, unknownFutureValue. + MemberType string `json:"memberType"` + + // The identifier of the privilegedAccessGroupEligibilitySchedule from which this instance was created. + EligibilityScheduleId string `json:"eligibilityScheduleId"` +} + +type PrivilegedAccessGroupEligibilityScheduleInstanceList struct { + Count int `json:"@odata.count,omitempty"` // The total count of all results + NextLink string `json:"@odata.nextLink,omitempty"` // The URL to use for getting the next set of values. + Value []PrivilegedAccessGroupEligibilityScheduleInstance `json:"value"` // A list of role assignments. +} + +type PrivilegedAccessGroupEligibilityScheduleInstanceResult struct { + Error error + Ok PrivilegedAccessGroupEligibilityScheduleInstance +} diff --git a/models/azure/unified_role_eligibility_schedule_instance.go b/models/azure/unified_role_eligibility_schedule_instance.go new file mode 100644 index 0000000..be7c969 --- /dev/null +++ b/models/azure/unified_role_eligibility_schedule_instance.go @@ -0,0 +1,69 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package azure + +type UnifiedRoleEligibilityScheduleInstance struct { + Entity + + // The status of the role eligibility request. + // Read-only. + // Supports $filter (eq, ne). + Status string `json:"status"` + + // Identifier of the principal that has been granted the role eligibility. + // Can be a user or a role-assignable group. You can grant only active + // assignments service principals. + // Supports $filter (eq, in). + PrincipalId string `json:"principalId"` + + // Identifier of the unifiedRoleDefinition object that is being assigned to the principal. + // Supports $filer (eq, in). + RoleDefinitionId string `json:"roleDefinitionId"` + + // Identifier of the directory object representing the scope of the role eligibility. + // Either this property or appScopeId is required. + // The scope of a role eligibility determines the set of resources for which the principal has been granted access. + // Directory scopes are shared scopes stored in the directory that are understood by multiple applications. + // + // Use / for tenant-wide scope. + // Use appScopeId to limit the scope to an application only. + // + // Supports $filter (eq, ne, and on null values). + DirectoryScopeId string `json:"directoryScopeId,omitempty"` + + // Identifier of the app-specific scope when the role eligibility scope is scoped to an app. + // The scope of a role eligibility determines the set of resources for which the principal is eligible to access. + // App scopes are scopes that are defined and understood by this application only. + // + // Use / for tenant-wide app scopes. + // Use directoryScopeId to limit the scope to particular directory objects, for example, administrative units. + // + // Supports $filter (eq, ne, and on null values). + AppScopeId string `json:"appScopeId,omitempty"` +} + +type UnifiedRoleEligibilityScheduleInstanceList struct { + Count int `json:"@odata.count,omitempty"` // The total count of all results + NextLink string `json:"@odata.nextLink,omitempty"` // The URL to use for getting the next set of values. + Value []UnifiedRoleEligibilityScheduleInstance `json:"value"` // A list of role assignments. +} + +type UnifiedRoleEligibilityScheduleInstanceResult struct { + Error error + Ok UnifiedRoleEligibilityScheduleInstance +} diff --git a/models/group-eligibility-schedule-instances.go b/models/group-eligibility-schedule-instances.go new file mode 100644 index 0000000..7fa60a0 --- /dev/null +++ b/models/group-eligibility-schedule-instances.go @@ -0,0 +1,28 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "github.com/bloodhoundad/azurehound/v2/models/azure" +) + +type GroupEligibilityScheduleInstances struct { + GroupEligibilityScheduleInstances []azure.PrivilegedAccessGroupEligibilityScheduleInstance `json:"GroupEligibilityScheduleInstances"` + GroupId string `json:"groupId"` + TenantId string `json:"tenantId"` +} diff --git a/models/role-eligibility-schedule-instances.go b/models/role-eligibility-schedule-instances.go new file mode 100644 index 0000000..1cdc576 --- /dev/null +++ b/models/role-eligibility-schedule-instances.go @@ -0,0 +1,28 @@ +// Copyright (C) 2022 Specter Ops, Inc. +// +// This file is part of AzureHound. +// +// AzureHound is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// AzureHound is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package models + +import ( + "github.com/bloodhoundad/azurehound/v2/models/azure" +) + +type RoleEligibilityScheduleInstances struct { + RoleEligibilityScheduleInstances []azure.UnifiedRoleEligibilityScheduleInstance `json:"RoleEligibilityScheduleInstances"` + RoleDefinitionId string `json:"roleDefinitionId"` + TenantId string `json:"tenantId"` +}