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"`
+}