From aca23d3b93198e2c75f260ce2ba3d76fb9bccde1 Mon Sep 17 00:00:00 2001 From: "omri.s" Date: Sun, 24 Nov 2024 15:30:34 +0200 Subject: [PATCH 01/15] Support Azure actions and data actions --- .../api/v1beta1/clientintents_types.go | 9 ++++ src/operator/api/v1beta1/webhooks.go | 42 ++++++++++------- src/operator/api/v1beta1/webhooks_test.go | 45 +++++++++++++++++++ .../api/v1beta1/zz_generated.deepcopy.go | 10 +++++ .../api/v2alpha1/clientintents_types.go | 11 ++++- .../api/v2alpha1/zz_generated.deepcopy.go | 10 +++++ .../k8s.otterize.com_clientintents.patched | 16 +++++++ .../crd/k8s.otterize.com_clientintents.yaml | 16 +++++++ ...lientintents-customresourcedefinition.yaml | 16 +++++++ .../clientintents_webhook_v2alpha1.go | 18 ++++++++ 10 files changed, 177 insertions(+), 16 deletions(-) diff --git a/src/operator/api/v1beta1/clientintents_types.go b/src/operator/api/v1beta1/clientintents_types.go index 34f8cde0e..25f44f732 100644 --- a/src/operator/api/v1beta1/clientintents_types.go +++ b/src/operator/api/v1beta1/clientintents_types.go @@ -258,6 +258,12 @@ type Intent struct { //+optional AzureRoles []string `json:"azureRoles,omitempty" yaml:"azureRoles,omitempty"` + //+optional + AzureDataActions []AzureDataAction `json:"azureDataActions,omitempty" yaml:"azureDataActions,omitempty"` + + //+optional + AzureActions []AzureAction `json:"azureActions,omitempty" yaml:"azureActions,omitempty"` + //+optional AzureKeyVaultPolicy *AzureKeyVaultPolicy `json:"azureKeyVaultPolicy,omitempty" yaml:"azureKeyVaultPolicy,omitempty"` @@ -265,6 +271,9 @@ type Intent struct { Internet *Internet `json:"internet,omitempty" yaml:"internet,omitempty"` } +type AzureDataAction string +type AzureAction string + type Internet struct { //+optional Domains []string `json:"domains,omitempty" yaml:"domains,omitempty"` diff --git a/src/operator/api/v1beta1/webhooks.go b/src/operator/api/v1beta1/webhooks.go index 7850da4bc..73d6bf6b1 100644 --- a/src/operator/api/v1beta1/webhooks.go +++ b/src/operator/api/v1beta1/webhooks.go @@ -248,22 +248,28 @@ func (in *ClientIntents) ConvertTo(dstRaw conversion.Hub) error { } if call.Type == IntentTypeAzure { dst.Spec.Targets[i] = v2alpha1.Target{Azure: lo.ToPtr(v2alpha1.AzureTarget{Scope: call.Name, Roles: call.AzureRoles})} - if call.AzureKeyVaultPolicy == nil { - continue + if len(call.AzureActions) > 0 { + dst.Spec.Targets[i].Azure.Actions = lo.Map(call.AzureActions, func(action AzureAction, _ int) v2alpha1.AzureAction { return v2alpha1.AzureAction(action) }) + } + if len(call.AzureDataActions) > 0 { + dst.Spec.Targets[i].Azure.DataActions = lo.Map(call.AzureDataActions, func(action AzureDataAction, _ int) v2alpha1.AzureDataAction { return v2alpha1.AzureDataAction(action) }) + } + + if call.AzureKeyVaultPolicy != nil { + dst.Spec.Targets[i].Azure.KeyVaultPolicy = &v2alpha1.AzureKeyVaultPolicy{} + dst.Spec.Targets[i].Azure.KeyVaultPolicy.KeyPermissions = lo.Map(call.AzureKeyVaultPolicy.KeyPermissions, func(permission AzureKeyVaultKeyPermission, _ int) v2alpha1.AzureKeyVaultKeyPermission { + return v2alpha1.AzureKeyVaultKeyPermission(permission) + }) + dst.Spec.Targets[i].Azure.KeyVaultPolicy.SecretPermissions = lo.Map(call.AzureKeyVaultPolicy.SecretPermissions, func(permission AzureKeyVaultSecretPermission, _ int) v2alpha1.AzureKeyVaultSecretPermission { + return v2alpha1.AzureKeyVaultSecretPermission(permission) + }) + dst.Spec.Targets[i].Azure.KeyVaultPolicy.CertificatePermissions = lo.Map(call.AzureKeyVaultPolicy.CertificatePermissions, func(permission AzureKeyVaultCertificatePermission, _ int) v2alpha1.AzureKeyVaultCertificatePermission { + return v2alpha1.AzureKeyVaultCertificatePermission(permission) + }) + dst.Spec.Targets[i].Azure.KeyVaultPolicy.StoragePermissions = lo.Map(call.AzureKeyVaultPolicy.StoragePermissions, func(permission AzureKeyVaultStoragePermission, _ int) v2alpha1.AzureKeyVaultStoragePermission { + return v2alpha1.AzureKeyVaultStoragePermission(permission) + }) } - dst.Spec.Targets[i].Azure.KeyVaultPolicy = &v2alpha1.AzureKeyVaultPolicy{} - dst.Spec.Targets[i].Azure.KeyVaultPolicy.KeyPermissions = lo.Map(call.AzureKeyVaultPolicy.KeyPermissions, func(permission AzureKeyVaultKeyPermission, _ int) v2alpha1.AzureKeyVaultKeyPermission { - return v2alpha1.AzureKeyVaultKeyPermission(permission) - }) - dst.Spec.Targets[i].Azure.KeyVaultPolicy.SecretPermissions = lo.Map(call.AzureKeyVaultPolicy.SecretPermissions, func(permission AzureKeyVaultSecretPermission, _ int) v2alpha1.AzureKeyVaultSecretPermission { - return v2alpha1.AzureKeyVaultSecretPermission(permission) - }) - dst.Spec.Targets[i].Azure.KeyVaultPolicy.CertificatePermissions = lo.Map(call.AzureKeyVaultPolicy.CertificatePermissions, func(permission AzureKeyVaultCertificatePermission, _ int) v2alpha1.AzureKeyVaultCertificatePermission { - return v2alpha1.AzureKeyVaultCertificatePermission(permission) - }) - dst.Spec.Targets[i].Azure.KeyVaultPolicy.StoragePermissions = lo.Map(call.AzureKeyVaultPolicy.StoragePermissions, func(permission AzureKeyVaultStoragePermission, _ int) v2alpha1.AzureKeyVaultStoragePermission { - return v2alpha1.AzureKeyVaultStoragePermission(permission) - }) } if call.Type == IntentTypeInternet && call.Internet != nil { dst.Spec.Targets[i] = v2alpha1.Target{Internet: lo.ToPtr(v2alpha1.Internet{Domains: call.Internet.Domains, Ports: call.Internet.Ports, Ips: call.Internet.Ips})} @@ -335,6 +341,12 @@ func (in *ClientIntents) ConvertFrom(srcRaw conversion.Hub) error { } if target.Azure != nil { in.Spec.Calls[i] = Intent{Type: IntentTypeAzure, Name: target.Azure.Scope, AzureRoles: target.Azure.Roles} + if len(target.Azure.Actions) > 0 { + in.Spec.Calls[i].AzureActions = lo.Map(target.Azure.Actions, func(action v2alpha1.AzureAction, _ int) AzureAction { return AzureAction(action) }) + } + if len(target.Azure.DataActions) > 0 { + in.Spec.Calls[i].AzureDataActions = lo.Map(target.Azure.DataActions, func(action v2alpha1.AzureDataAction, _ int) AzureDataAction { return AzureDataAction(action) }) + } if target.Azure.KeyVaultPolicy == nil { continue } diff --git a/src/operator/api/v1beta1/webhooks_test.go b/src/operator/api/v1beta1/webhooks_test.go index f7b06c243..3fb6809b0 100644 --- a/src/operator/api/v1beta1/webhooks_test.go +++ b/src/operator/api/v1beta1/webhooks_test.go @@ -241,6 +241,51 @@ func (t *WebhooksTestSuite) TestClientIntentsFromV2_EmptySliceHTTPShouldNotBeTyp t.Require().Equal("", string(converted.Spec.Calls[1].Type)) } +func (t *WebhooksTestSuite) TestClientIntentsAzureActionsDataActions() { + // Create a ClientIntents with random data + original := &ClientIntents{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: &IntentsSpec{ + Service: Service{ + Name: "test", + }, + Calls: []Intent{ + { + Name: "testscope1", + Type: IntentTypeAzure, + AzureDataActions: []AzureDataAction{ + "testDataAction1", + "testDataAction2", + }, + }, + { + Name: "testscope2", + Type: IntentTypeAzure, + AzureActions: []AzureAction{ + "testAction1", + "testAction2", + }, + }, + }, + }, + } + + // ConvertTo + dstRaw := &v2alpha1.ClientIntents{} + err := original.ConvertTo(dstRaw) + t.Require().NoError(err) + + // ConvertFrom + converted := &ClientIntents{} + err = converted.ConvertFrom(dstRaw) + t.Require().NoError(err) + + t.Require().Equal(original.Spec, converted.Spec) +} + func TestWebhooksTestSuite(t *testing.T) { suite.Run(t, new(WebhooksTestSuite)) } diff --git a/src/operator/api/v1beta1/zz_generated.deepcopy.go b/src/operator/api/v1beta1/zz_generated.deepcopy.go index daeb78966..91946d021 100644 --- a/src/operator/api/v1beta1/zz_generated.deepcopy.go +++ b/src/operator/api/v1beta1/zz_generated.deepcopy.go @@ -236,6 +236,16 @@ func (in *Intent) DeepCopyInto(out *Intent) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.AzureDataActions != nil { + in, out := &in.AzureDataActions, &out.AzureDataActions + *out = make([]AzureDataAction, len(*in)) + copy(*out, *in) + } + if in.AzureActions != nil { + in, out := &in.AzureActions, &out.AzureActions + *out = make([]AzureAction, len(*in)) + copy(*out, *in) + } if in.AzureKeyVaultPolicy != nil { in, out := &in.AzureKeyVaultPolicy, &out.AzureKeyVaultPolicy *out = new(AzureKeyVaultPolicy) diff --git a/src/operator/api/v2alpha1/clientintents_types.go b/src/operator/api/v2alpha1/clientintents_types.go index 3bfe221f4..cac1675a7 100644 --- a/src/operator/api/v2alpha1/clientintents_types.go +++ b/src/operator/api/v2alpha1/clientintents_types.go @@ -276,12 +276,21 @@ type GCPTarget struct { } type AzureTarget struct { - Scope string `json:"scope,omitempty" yaml:"scope,omitempty"` + Scope string `json:"scope,omitempty" yaml:"scope,omitempty"` + //+optional Roles []string `json:"roles,omitempty" yaml:"roles,omitempty"` //+optional KeyVaultPolicy *AzureKeyVaultPolicy `json:"keyVaultPolicy,omitempty" yaml:"keyVaultPolicy,omitempty"` + //+optional + Actions []AzureAction `json:"actions,omitempty" yaml:"actions,omitempty"` + //+optional + DataActions []AzureDataAction `json:"dataActions,omitempty" yaml:"dataActions,omitempty"` } +type AzureAction string + +type AzureDataAction string + type KubernetesTarget struct { Name string `json:"name" yaml:"name"` Kind string `json:"kind" yaml:"kind"` diff --git a/src/operator/api/v2alpha1/zz_generated.deepcopy.go b/src/operator/api/v2alpha1/zz_generated.deepcopy.go index 8b7226a53..5d463e279 100644 --- a/src/operator/api/v2alpha1/zz_generated.deepcopy.go +++ b/src/operator/api/v2alpha1/zz_generated.deepcopy.go @@ -92,6 +92,16 @@ func (in *AzureTarget) DeepCopyInto(out *AzureTarget) { *out = new(AzureKeyVaultPolicy) (*in).DeepCopyInto(*out) } + if in.Actions != nil { + in, out := &in.Actions, &out.Actions + *out = make([]AzureAction, len(*in)) + copy(*out, *in) + } + if in.DataActions != nil { + in, out := &in.DataActions, &out.DataActions + *out = make([]AzureDataAction, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureTarget. diff --git a/src/operator/config/crd/k8s.otterize.com_clientintents.patched b/src/operator/config/crd/k8s.otterize.com_clientintents.patched index fbf5c934f..d9839b08c 100644 --- a/src/operator/config/crd/k8s.otterize.com_clientintents.patched +++ b/src/operator/config/crd/k8s.otterize.com_clientintents.patched @@ -485,6 +485,14 @@ spec: items: type: string type: array + azureActions: + items: + type: string + type: array + azureDataActions: + items: + type: string + type: array azureKeyVaultPolicy: properties: certificatePermissions: @@ -737,6 +745,14 @@ spec: type: object azure: properties: + actions: + items: + type: string + type: array + dataActions: + items: + type: string + type: array keyVaultPolicy: properties: certificatePermissions: diff --git a/src/operator/config/crd/k8s.otterize.com_clientintents.yaml b/src/operator/config/crd/k8s.otterize.com_clientintents.yaml index c4c800c31..2f4aa253b 100644 --- a/src/operator/config/crd/k8s.otterize.com_clientintents.yaml +++ b/src/operator/config/crd/k8s.otterize.com_clientintents.yaml @@ -473,6 +473,14 @@ spec: items: type: string type: array + azureActions: + items: + type: string + type: array + azureDataActions: + items: + type: string + type: array azureKeyVaultPolicy: properties: certificatePermissions: @@ -726,6 +734,14 @@ spec: type: object azure: properties: + actions: + items: + type: string + type: array + dataActions: + items: + type: string + type: array keyVaultPolicy: properties: certificatePermissions: diff --git a/src/operator/otterizecrds/clientintents-customresourcedefinition.yaml b/src/operator/otterizecrds/clientintents-customresourcedefinition.yaml index fbf5c934f..d9839b08c 100644 --- a/src/operator/otterizecrds/clientintents-customresourcedefinition.yaml +++ b/src/operator/otterizecrds/clientintents-customresourcedefinition.yaml @@ -485,6 +485,14 @@ spec: items: type: string type: array + azureActions: + items: + type: string + type: array + azureDataActions: + items: + type: string + type: array azureKeyVaultPolicy: properties: certificatePermissions: @@ -737,6 +745,14 @@ spec: type: object azure: properties: + actions: + items: + type: string + type: array + dataActions: + items: + type: string + type: array keyVaultPolicy: properties: certificatePermissions: diff --git a/src/operator/webhooks/clientintents_webhook_v2alpha1.go b/src/operator/webhooks/clientintents_webhook_v2alpha1.go index eeb7a3d22..dd673a117 100644 --- a/src/operator/webhooks/clientintents_webhook_v2alpha1.go +++ b/src/operator/webhooks/clientintents_webhook_v2alpha1.go @@ -385,6 +385,24 @@ func (v *IntentsValidatorV2alpha1) validateAzureTarget(azureTarget *otterizev2al Detail: "invalid intent format, field scope is required", } } + // check that at least one of the optional fields is set + if len(azureTarget.Actions) == 0 && len(azureTarget.DataActions) == 0 && len(azureTarget.Roles) == 0 && azureTarget.KeyVaultPolicy == nil { + return &field.Error{ + Type: field.ErrorTypeRequired, + Field: "actions", + Detail: "invalid intent format, at least one of [actions, dataActions, roles, keyVaultPolicy] must be set", + } + } + + // check that that if intents uses actions/dataActions then roles must be empty (and vice versa) + if (len(azureTarget.Actions) > 0 || len(azureTarget.DataActions) > 0) && len(azureTarget.Roles) > 0 { + return &field.Error{ + Type: field.ErrorTypeRequired, + Field: "roles", + Detail: "invalid intent format, if actions or dataActions are set, roles must be empty", + } + } + return nil } From 7d3ada59a04f667248d76ce5ccb6b79c1707cca4 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Mon, 25 Nov 2024 15:24:11 +0200 Subject: [PATCH 02/15] Add support for azure custom roles --- .../iampolicyagents/azurepolicyagent/agent.go | 49 +++++++- src/shared/azureagent/azureclients.go | 1 + src/shared/azureagent/customroles.go | 114 ++++++++++++++++++ 3 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 src/shared/azureagent/customroles.go diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go index ce108253e..0f18dc4f9 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go @@ -52,20 +52,30 @@ func (a *Agent) getIntentScope(intent otterizev2alpha1.Target) (string, error) { return fullScope, nil } -func (a *Agent) AddRolePolicyFromIntents(ctx context.Context, namespace string, accountName string, intentsServiceName string, intents []otterizev2alpha1.Target, pod corev1.Pod) error { +func (a *Agent) AddRolePolicyFromIntents(ctx context.Context, namespace string, _ string, intentsServiceName string, intents []otterizev2alpha1.Target, _ corev1.Pod) error { userAssignedIdentity, err := a.FindUserAssignedIdentity(ctx, namespace, intentsServiceName) if err != nil { return errors.Wrap(err) } + // Custom roles + azureCustomRolesIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { + hasCustomRoles := intent.Azure != nil && (len(intent.Azure.Actions) > 0 || len(intent.Azure.DataActions) > 0) + return hasCustomRoles && len(intent.Azure.Roles) == 0 + }) + if err := a.ensureCustomRolesForIntents(ctx, userAssignedIdentity, azureCustomRolesIntents); err != nil { + return errors.Wrap(err) + } + + // Backwards compatibility for role assignments azureRBACIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { return intent.Azure != nil && len(intent.Azure.Roles) > 0 }) - if err := a.ensureRoleAssignmentsForIntents(ctx, userAssignedIdentity, azureRBACIntents); err != nil { return errors.Wrap(err) } + // Key Vault permissions azureKeyVaultIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { return intent.Azure != nil && intent.Azure.KeyVaultPolicy != nil }) @@ -82,6 +92,11 @@ func (a *Agent) ensureRoleAssignmentsForIntents(ctx context.Context, userAssigne return errors.Wrap(err) } + // Filter out assignments on custom roles + existingRoleAssignments = lo.Filter(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment, _ int) bool { + return strings.HasPrefix(*roleAssignment.Properties.RoleDefinitionID, "/providers/Microsoft.Authorization/roleDefinitions") + }) + existingRoleAssignmentsByScope := lo.GroupBy(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment) string { return *roleAssignment.Properties.Scope }) @@ -192,7 +207,7 @@ func (a *Agent) DeleteRolePolicyFromIntents(ctx context.Context, intents otteriz return errors.Wrap(err) } - for keyVaultName, _ := range existingKeyVaultsAccessPolicies { + for keyVaultName := range existingKeyVaultsAccessPolicies { if err := a.RemoveKeyVaultAccessPolicy(ctx, keyVaultName, userAssignedIdentity); err != nil { return errors.Wrap(err) } @@ -301,3 +316,31 @@ func (a *Agent) vaultAccessPolicyEntryFromIntent(userAssignedIdentity armmsi.Ide }, } } + +func (a *Agent) ensureCustomRolesForIntents(ctx context.Context, userAssignedIdentity armmsi.Identity, intents []otterizev2alpha1.Target) error { + for _, intent := range intents { + scope, err := a.getIntentScope(intent) + if err != nil { + return errors.Wrap(err) + } + + actions := intent.Azure.Actions + dataActions := intent.Azure.DataActions + + customRoleName := a.GenerateCustomRoleName(userAssignedIdentity, scope) + role, found := a.FindCustomRoleByName(ctx, customRoleName) + if found { + err = a.UpdateCustomRole(ctx, role, actions, dataActions) + if err != nil { + return errors.Wrap(err) + } + } else { + err = a.CreateCustomRole(ctx, scope, userAssignedIdentity, actions, dataActions) + if err != nil { + return errors.Wrap(err) + } + } + } + + return nil +} diff --git a/src/shared/azureagent/azureclients.go b/src/shared/azureagent/azureclients.go index 430069a8b..3f65709c8 100644 --- a/src/shared/azureagent/azureclients.go +++ b/src/shared/azureagent/azureclients.go @@ -36,6 +36,7 @@ type AzureARMMSIFederatedIdentityCredentialsClient interface { } type AzureARMAuthorizationRoleDefinitionsClient interface { + CreateOrUpdate(ctx context.Context, scope string, roleDefinitionID string, roleDefinition armauthorization.RoleDefinition, options *armauthorization.RoleDefinitionsClientCreateOrUpdateOptions) (armauthorization.RoleDefinitionsClientCreateOrUpdateResponse, error) NewListPager(scope string, options *armauthorization.RoleDefinitionsClientListOptions) *runtime.Pager[armauthorization.RoleDefinitionsClientListResponse] } diff --git a/src/shared/azureagent/customroles.go b/src/shared/azureagent/customroles.go new file mode 100644 index 000000000..5022c48eb --- /dev/null +++ b/src/shared/azureagent/customroles.go @@ -0,0 +1,114 @@ +package azureagent + +import ( + "context" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" + "github.com/google/uuid" + "github.com/otterize/intents-operator/src/operator/api/v2alpha1" + "github.com/otterize/intents-operator/src/shared/agentutils" + "github.com/otterize/intents-operator/src/shared/errors" + "github.com/samber/lo" +) + +const ( + // maxRoleNameLength rules: 3-512 characters + maxRoleNameLength = 200 +) + +func (a *Agent) getCustomRoleScope() string { + return fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", a.Conf.SubscriptionID, a.Conf.ResourceGroup) +} + +func (a *Agent) GenerateCustomRoleName(uai armmsi.Identity, scope string) string { + fullName := fmt.Sprintf("%s-%s", *uai.Name, scope) + return agentutils.TruncateHashName(fullName, maxRoleNameLength) +} + +func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.Identity, actions []v2alpha1.AzureAction, dataActions []v2alpha1.AzureDataAction) error { + roleScope := a.getCustomRoleScope() + + formattedActions := lo.Map(actions, func(action v2alpha1.AzureAction, _ int) *string { + return to.Ptr(string(action)) + }) + formattedDataActions := lo.Map(dataActions, func(action v2alpha1.AzureDataAction, _ int) *string { + return to.Ptr(string(action)) + }) + + id := uuid.NewString() + name := a.GenerateCustomRoleName(uai, scope) + description := fmt.Sprintf("Otterize managed custom role for uai [%s] with permissions for scope [%s]", *uai.Name, scope) + + roleDefinition := armauthorization.RoleDefinition{ + Properties: &armauthorization.RoleDefinitionProperties{ + RoleName: to.Ptr(name), + Description: to.Ptr(description), + AssignableScopes: []*string{to.Ptr(scope)}, // Where the role can be assigned + Permissions: []*armauthorization.Permission{ + { + Actions: formattedActions, + DataActions: formattedDataActions, + }, + }, + }, + } + + // create the custom role + resp, err := a.roleDefinitionsClient.CreateOrUpdate(ctx, roleScope, id, roleDefinition, nil) + if err != nil { + return errors.Wrap(err) + } + + // create a role assignment for the custom role + err = a.CreateRoleAssignment(ctx, scope, uai, resp.RoleDefinition) + if err != nil { + return errors.Wrap(err) + } + + return nil +} + +func (a *Agent) UpdateCustomRole(ctx context.Context, role *armauthorization.RoleDefinition, actions []v2alpha1.AzureAction, dataActions []v2alpha1.AzureDataAction) error { + roleScope := a.getCustomRoleScope() + + formattedActions := lo.Map(actions, func(action v2alpha1.AzureAction, _ int) *string { + return to.Ptr(string(action)) + }) + formattedDataActions := lo.Map(dataActions, func(action v2alpha1.AzureDataAction, _ int) *string { + return to.Ptr(string(action)) + }) + + role.Properties.Permissions = []*armauthorization.Permission{ + { + Actions: formattedActions, + DataActions: formattedDataActions, + }, + } + + _, err := a.roleDefinitionsClient.CreateOrUpdate(ctx, roleScope, *role.Name, *role, nil) + if err != nil { + return errors.Wrap(err) + } + + return nil +} + +func (a *Agent) FindCustomRoleByName(ctx context.Context, name string) (*armauthorization.RoleDefinition, bool) { + scope := a.getCustomRoleScope() + filter := fmt.Sprintf("roleName eq '%s'", name) + + pager := a.roleDefinitionsClient.NewListPager(scope, &armauthorization.RoleDefinitionsClientListOptions{ + Filter: &filter, + }) + + for pager.More() { + page, _ := pager.NextPage(ctx) + for _, role := range page.Value { + return role, true + } + } + + return nil, false +} From 216de6926b8e11d01cb2753f40e9a4990bea50f0 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Mon, 25 Nov 2024 15:32:32 +0200 Subject: [PATCH 03/15] go generate --- src/shared/azureagent/mocks/azureclients.go | 15 + .../otterizecloud/graphqlclient/generated.go | 350 ++++++++---------- .../graphqlclient/schema.graphql | 70 ++-- .../telemetries/telemetriesgql/generated.go | 70 ++-- .../telemetries/telemetriesgql/schema.graphql | 70 ++-- 5 files changed, 285 insertions(+), 290 deletions(-) diff --git a/src/shared/azureagent/mocks/azureclients.go b/src/shared/azureagent/mocks/azureclients.go index 4f55a6f27..e9f244344 100644 --- a/src/shared/azureagent/mocks/azureclients.go +++ b/src/shared/azureagent/mocks/azureclients.go @@ -291,6 +291,21 @@ func (m *MockAzureARMAuthorizationRoleDefinitionsClient) EXPECT() *MockAzureARMA return m.recorder } +// CreateOrUpdate mocks base method. +func (m *MockAzureARMAuthorizationRoleDefinitionsClient) CreateOrUpdate(ctx context.Context, scope, roleDefinitionID string, roleDefinition armauthorization.RoleDefinition, options *armauthorization.RoleDefinitionsClientCreateOrUpdateOptions) (armauthorization.RoleDefinitionsClientCreateOrUpdateResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOrUpdate", ctx, scope, roleDefinitionID, roleDefinition, options) + ret0, _ := ret[0].(armauthorization.RoleDefinitionsClientCreateOrUpdateResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOrUpdate indicates an expected call of CreateOrUpdate. +func (mr *MockAzureARMAuthorizationRoleDefinitionsClientMockRecorder) CreateOrUpdate(ctx, scope, roleDefinitionID, roleDefinition, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockAzureARMAuthorizationRoleDefinitionsClient)(nil).CreateOrUpdate), ctx, scope, roleDefinitionID, roleDefinition, options) +} + // NewListPager mocks base method. func (m *MockAzureARMAuthorizationRoleDefinitionsClient) NewListPager(scope string, options *armauthorization.RoleDefinitionsClientListOptions) *runtime.Pager[armauthorization.RoleDefinitionsClientListResponse] { m.ctrl.T.Helper() diff --git a/src/shared/otterizecloud/graphqlclient/generated.go b/src/shared/otterizecloud/graphqlclient/generated.go index 9721b3775..decc39955 100644 --- a/src/shared/otterizecloud/graphqlclient/generated.go +++ b/src/shared/otterizecloud/graphqlclient/generated.go @@ -879,340 +879,310 @@ type dummyResponse struct { // GetDummyError returns dummyResponse.DummyError, and is useful for accessing the field via an interface. func (v *dummyResponse) GetDummyError() UserErrorType { return v.DummyError } -// The query or mutation executed by ReportAppliedKubernetesIntents. -const ReportAppliedKubernetesIntents_Operation = ` -mutation ReportAppliedKubernetesIntents ($namespace: String!, $intents: [IntentInput!]!, $clusterId: String!) { - reportAppliedKubernetesIntents(namespace: $namespace, intents: $intents, ossClusterId: $clusterId) -} -` - func ReportAppliedKubernetesIntents( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, namespace *string, intents []*IntentInput, clusterId *string, ) (*ReportAppliedKubernetesIntentsResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportAppliedKubernetesIntents", - Query: ReportAppliedKubernetesIntents_Operation, + Query: ` +mutation ReportAppliedKubernetesIntents ($namespace: String!, $intents: [IntentInput!]!, $clusterId: String!) { + reportAppliedKubernetesIntents(namespace: $namespace, intents: $intents, ossClusterId: $clusterId) +} +`, Variables: &__ReportAppliedKubernetesIntentsInput{ Namespace: namespace, Intents: intents, ClusterId: clusterId, }, } - var err_ error + var err error - var data_ ReportAppliedKubernetesIntentsResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportAppliedKubernetesIntentsResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ -} - -// The query or mutation executed by ReportClientIntentEvents. -const ReportClientIntentEvents_Operation = ` -mutation ReportClientIntentEvents ($events: [ClientIntentEventInput!]!) { - reportClientIntentEvent(events: $events) + return &data, err } -` func ReportClientIntentEvents( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, events []ClientIntentEventInput, ) (*ReportClientIntentEventsResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportClientIntentEvents", - Query: ReportClientIntentEvents_Operation, + Query: ` +mutation ReportClientIntentEvents ($events: [ClientIntentEventInput!]!) { + reportClientIntentEvent(events: $events) +} +`, Variables: &__ReportClientIntentEventsInput{ Events: events, }, } - var err_ error + var err error - var data_ ReportClientIntentEventsResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportClientIntentEventsResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ -} - -// The query or mutation executed by ReportClientIntentStatuses. -const ReportClientIntentStatuses_Operation = ` -mutation ReportClientIntentStatuses ($statuses: [ClientIntentStatusInput!]!) { - reportClientIntentStatus(statuses: $statuses) + return &data, err } -` func ReportClientIntentStatuses( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, statuses []ClientIntentStatusInput, ) (*ReportClientIntentStatusesResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportClientIntentStatuses", - Query: ReportClientIntentStatuses_Operation, + Query: ` +mutation ReportClientIntentStatuses ($statuses: [ClientIntentStatusInput!]!) { + reportClientIntentStatus(statuses: $statuses) +} +`, Variables: &__ReportClientIntentStatusesInput{ Statuses: statuses, }, } - var err_ error + var err error - var data_ ReportClientIntentStatusesResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportClientIntentStatusesResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ + return &data, err } -// The query or mutation executed by ReportComponentStatus. -const ReportComponentStatus_Operation = ` -mutation ReportComponentStatus ($component: ComponentType!) { - reportIntegrationComponentStatus(component: $component) -} -` - func ReportComponentStatus( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, component ComponentType, ) (*ReportComponentStatusResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportComponentStatus", - Query: ReportComponentStatus_Operation, + Query: ` +mutation ReportComponentStatus ($component: ComponentType!) { + reportIntegrationComponentStatus(component: $component) +} +`, Variables: &__ReportComponentStatusInput{ Component: component, }, } - var err_ error + var err error - var data_ ReportComponentStatusResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportComponentStatusResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ -} - -// The query or mutation executed by ReportExternallyAccessibleServices. -const ReportExternallyAccessibleServices_Operation = ` -mutation ReportExternallyAccessibleServices ($namespace: String!, $services: [ExternallyAccessibleServiceInput!]!) { - reportExternallyAccessibleServices(namespace: $namespace, services: $services) + return &data, err } -` func ReportExternallyAccessibleServices( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, namespace string, services []ExternallyAccessibleServiceInput, ) (*ReportExternallyAccessibleServicesResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportExternallyAccessibleServices", - Query: ReportExternallyAccessibleServices_Operation, + Query: ` +mutation ReportExternallyAccessibleServices ($namespace: String!, $services: [ExternallyAccessibleServiceInput!]!) { + reportExternallyAccessibleServices(namespace: $namespace, services: $services) +} +`, Variables: &__ReportExternallyAccessibleServicesInput{ Namespace: namespace, Services: services, }, } - var err_ error + var err error - var data_ ReportExternallyAccessibleServicesResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportExternallyAccessibleServicesResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ + return &data, err } -// The query or mutation executed by ReportIntentsOperatorConfiguration. -const ReportIntentsOperatorConfiguration_Operation = ` -mutation ReportIntentsOperatorConfiguration ($configuration: IntentsOperatorConfigurationInput!) { - reportIntentsOperatorConfiguration(configuration: $configuration) -} -` - func ReportIntentsOperatorConfiguration( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, configuration IntentsOperatorConfigurationInput, ) (*ReportIntentsOperatorConfigurationResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportIntentsOperatorConfiguration", - Query: ReportIntentsOperatorConfiguration_Operation, + Query: ` +mutation ReportIntentsOperatorConfiguration ($configuration: IntentsOperatorConfigurationInput!) { + reportIntentsOperatorConfiguration(configuration: $configuration) +} +`, Variables: &__ReportIntentsOperatorConfigurationInput{ Configuration: configuration, }, } - var err_ error + var err error - var data_ ReportIntentsOperatorConfigurationResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportIntentsOperatorConfigurationResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ -} - -// The query or mutation executed by ReportKafkaServerConfig. -const ReportKafkaServerConfig_Operation = ` -mutation ReportKafkaServerConfig ($namespace: String!, $kafkaServerConfigs: [KafkaServerConfigInput!]!) { - reportKafkaServerConfigs(namespace: $namespace, serverConfigs: $kafkaServerConfigs) + return &data, err } -` func ReportKafkaServerConfig( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, namespace string, kafkaServerConfigs []KafkaServerConfigInput, ) (*ReportKafkaServerConfigResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportKafkaServerConfig", - Query: ReportKafkaServerConfig_Operation, + Query: ` +mutation ReportKafkaServerConfig ($namespace: String!, $kafkaServerConfigs: [KafkaServerConfigInput!]!) { + reportKafkaServerConfigs(namespace: $namespace, serverConfigs: $kafkaServerConfigs) +} +`, Variables: &__ReportKafkaServerConfigInput{ Namespace: namespace, KafkaServerConfigs: kafkaServerConfigs, }, } - var err_ error + var err error - var data_ ReportKafkaServerConfigResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportKafkaServerConfigResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ -} - -// The query or mutation executed by ReportNetworkPolicies. -const ReportNetworkPolicies_Operation = ` -mutation ReportNetworkPolicies ($namespace: String!, $policies: [NetworkPolicyInput!]!) { - reportNetworkPolicies(namespace: $namespace, policies: $policies) + return &data, err } -` func ReportNetworkPolicies( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, namespace string, policies []NetworkPolicyInput, ) (*ReportNetworkPoliciesResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportNetworkPolicies", - Query: ReportNetworkPolicies_Operation, + Query: ` +mutation ReportNetworkPolicies ($namespace: String!, $policies: [NetworkPolicyInput!]!) { + reportNetworkPolicies(namespace: $namespace, policies: $policies) +} +`, Variables: &__ReportNetworkPoliciesInput{ Namespace: namespace, Policies: policies, }, } - var err_ error + var err error - var data_ ReportNetworkPoliciesResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportNetworkPoliciesResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ + return &data, err } -// The query or mutation executed by ReportProtectedServicesSnapshot. -const ReportProtectedServicesSnapshot_Operation = ` -mutation ReportProtectedServicesSnapshot ($namespace: String!, $services: [ProtectedServiceInput!]!) { - reportProtectedServicesSnapshot(namespace: $namespace, services: $services) -} -` - func ReportProtectedServicesSnapshot( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, namespace string, services []ProtectedServiceInput, ) (*ReportProtectedServicesSnapshotResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportProtectedServicesSnapshot", - Query: ReportProtectedServicesSnapshot_Operation, + Query: ` +mutation ReportProtectedServicesSnapshot ($namespace: String!, $services: [ProtectedServiceInput!]!) { + reportProtectedServicesSnapshot(namespace: $namespace, services: $services) +} +`, Variables: &__ReportProtectedServicesSnapshotInput{ Namespace: namespace, Services: services, }, } - var err_ error + var err error - var data_ ReportProtectedServicesSnapshotResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportProtectedServicesSnapshotResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ -} - -// The query or mutation executed by dummy. -const dummy_Operation = ` -query dummy { - dummyError + return &data, err } -` func dummy( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, ) (*dummyResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "dummy", - Query: dummy_Operation, + Query: ` +query dummy { + dummyError +} +`, } - var err_ error + var err error - var data_ dummyResponse - resp_ := &graphql.Response{Data: &data_} + var data dummyResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ + return &data, err } diff --git a/src/shared/otterizecloud/graphqlclient/schema.graphql b/src/shared/otterizecloud/graphqlclient/schema.graphql index 5be8ab5a7..c8d65ca1a 100644 --- a/src/shared/otterizecloud/graphqlclient/schema.graphql +++ b/src/shared/otterizecloud/graphqlclient/schema.graphql @@ -147,7 +147,7 @@ type AccessGraphEdge { appliedIntents: [Intent!]! accessStatus: EdgeAccessStatus! accessStatuses: EdgeAccessStatuses! - violations: [CallViolation!]! + findings: [CallFinding!]! } """ Access graph filter """ @@ -158,6 +158,7 @@ type AccessGraphFilter { environmentIds: IDFilterValue lastSeen: TimeFilterValue featureFlags: FeatureFlags + includeOnlyClientsMatchingFilter: Boolean } type AccessLog { @@ -271,7 +272,7 @@ input CLITelemetry { command: CLICommand! } -type CallViolation { +type CallFinding { standard: RegulationStandard! code: RegulationCode! reason: String! @@ -408,6 +409,14 @@ input ClusterConfigurationInput { clusterFormSettings: ClusterFormSettingsInput } +type ClusterFinding { + cluster: Cluster! + reason: String! + reasonId: String! + intentsOperatorState: IntentsOperatorState + relatedServices: [Service!] +} + type ClusterFormSettings { certificateProvider: CertificateProvider! enforcement: Boolean! @@ -418,13 +427,6 @@ input ClusterFormSettingsInput { enforcement: Boolean! } -type ClusterViolation { - cluster: Cluster! - reason: String! - intentsOperatorState: IntentsOperatorState - relatedServices: [Service!] -} - input Component { componentType: TelemetryComponentType! componentInstanceId: ID! @@ -485,8 +487,17 @@ input DNSIPPairInput { } type Dashboard { - findings: [Finding!]! - summary: FindingsSummary! + findings: [FindingSummary!]! + summary: DashboardData! +} + +type DashboardData { + findingsCount: Int! + nonCompliantClusters: Fraction! + nonCompliantWorkloads: Fraction! + overallCompliance: Fraction! + nonCompliantStandards: Fraction! + nonCompliantControls: Fraction! } type DatabaseConfig { @@ -696,27 +707,18 @@ type FeatureFlags { isCloudSecurityEnabled: Boolean } -type Finding { +type FindingSummary { standard: RegulationStandard! codeLabel: String! code: RegulationCode! severity: Severity! description: String! validationDescription: String - clusterViolations: [ClusterViolation!]! - serviceViolations: [ServiceViolation!]! + clusterFindings: [ClusterFinding!]! + serviceFindings: [ServiceFinding!]! clusterCount: Int! serviceCount: Int! - requirements: [Finding!] -} - -type FindingsSummary { - violations: Int! - nonCompliantClusters: Fraction! - nonCompliantWorkloads: Fraction! - overallCompliance: Fraction! - nonCompliantStandards: Fraction! - nonCompliantControls: Fraction! + requirements: [FindingSummary!] } """The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).""" @@ -890,6 +892,7 @@ input InputAccessGraphFilter { environmentIds: InputIDFilterValue lastSeen: InputTimeFilterValue featureFlags: InputFeatureFlags + includeOnlyClientsMatchingFilter: Boolean } """ Access log filter """ @@ -1938,7 +1941,7 @@ type Query { findings( filter: InputFindingFilter tree: Boolean - ): [Finding!]! + ): [FindingSummary!]! dashboard( filter: InputFindingFilter tree: Boolean @@ -2027,11 +2030,13 @@ type Query { namespaceId: ID name: String filter: InputServiceFilter + featureFlags: InputFeatureFlags ): [Service!]! """Paginate services""" paginateServices( filter: InputServiceFilter pagination: PaginationInput + featureFlags: InputFeatureFlags ): ServicesResponse! """Get service by filters""" oneService( @@ -2168,6 +2173,8 @@ enum ServerProtectionStatusReason { PROTECTED_BY_DATABASE_INTEGRATION PROTECTED_BY_AWS_IAM_INTEGRATION PROTECTED_BY_INTERNET_INTENTS + SERVER_EXTERNAL_ACCESS_POLICY_CREATED + EXTERNALLY_MANAGED_POLICY_WORKLOAD } enum ServerProtectionStatusVerdict { @@ -2239,6 +2246,13 @@ enum ServiceExternalTrafficPolicy { LOCAL } +type ServiceFinding { + service: Service! + reason: String! + reasonId: String! + callsFindings: [AccessGraphEdge!] +} + input ServiceIdentityInput { name: String! namespace: String! @@ -2270,12 +2284,6 @@ enum ServiceType { DETECTED_CLOUD_SERVER } -type ServiceViolation { - service: Service! - reason: String! - violatedCalls: [AccessGraphEdge!] -} - type ServicesResponse { data: [Service!]! meta: PaginationMeta diff --git a/src/shared/telemetries/telemetriesgql/generated.go b/src/shared/telemetries/telemetriesgql/generated.go index 918cb6e9e..55678f3d9 100644 --- a/src/shared/telemetries/telemetriesgql/generated.go +++ b/src/shared/telemetries/telemetriesgql/generated.go @@ -184,70 +184,64 @@ type __SendTelemetriesInput struct { // GetTelemetries returns __SendTelemetriesInput.Telemetries, and is useful for accessing the field via an interface. func (v *__SendTelemetriesInput) GetTelemetries() []TelemetryInput { return v.Telemetries } -// The query or mutation executed by ReportErrors. -const ReportErrors_Operation = ` -mutation ReportErrors ($component: Component!, $errors: [Error!]!) { - sendErrors(component: $component, errors: $errors) -} -` - func ReportErrors( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, component *Component, errors []*Error, ) (*ReportErrorsResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "ReportErrors", - Query: ReportErrors_Operation, + Query: ` +mutation ReportErrors ($component: Component!, $errors: [Error!]!) { + sendErrors(component: $component, errors: $errors) +} +`, Variables: &__ReportErrorsInput{ Component: component, Errors: errors, }, } - var err_ error + var err error - var data_ ReportErrorsResponse - resp_ := &graphql.Response{Data: &data_} + var data ReportErrorsResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ + return &data, err } -// The query or mutation executed by SendTelemetries. -const SendTelemetries_Operation = ` -mutation SendTelemetries ($telemetries: [TelemetryInput!]!) { - sendTelemetries(telemetries: $telemetries) -} -` - func SendTelemetries( - ctx_ context.Context, - client_ graphql.Client, + ctx context.Context, + client graphql.Client, telemetries []TelemetryInput, ) (*SendTelemetriesResponse, error) { - req_ := &graphql.Request{ + req := &graphql.Request{ OpName: "SendTelemetries", - Query: SendTelemetries_Operation, + Query: ` +mutation SendTelemetries ($telemetries: [TelemetryInput!]!) { + sendTelemetries(telemetries: $telemetries) +} +`, Variables: &__SendTelemetriesInput{ Telemetries: telemetries, }, } - var err_ error + var err error - var data_ SendTelemetriesResponse - resp_ := &graphql.Response{Data: &data_} + var data SendTelemetriesResponse + resp := &graphql.Response{Data: &data} - err_ = client_.MakeRequest( - ctx_, - req_, - resp_, + err = client.MakeRequest( + ctx, + req, + resp, ) - return &data_, err_ + return &data, err } diff --git a/src/shared/telemetries/telemetriesgql/schema.graphql b/src/shared/telemetries/telemetriesgql/schema.graphql index 5be8ab5a7..c8d65ca1a 100644 --- a/src/shared/telemetries/telemetriesgql/schema.graphql +++ b/src/shared/telemetries/telemetriesgql/schema.graphql @@ -147,7 +147,7 @@ type AccessGraphEdge { appliedIntents: [Intent!]! accessStatus: EdgeAccessStatus! accessStatuses: EdgeAccessStatuses! - violations: [CallViolation!]! + findings: [CallFinding!]! } """ Access graph filter """ @@ -158,6 +158,7 @@ type AccessGraphFilter { environmentIds: IDFilterValue lastSeen: TimeFilterValue featureFlags: FeatureFlags + includeOnlyClientsMatchingFilter: Boolean } type AccessLog { @@ -271,7 +272,7 @@ input CLITelemetry { command: CLICommand! } -type CallViolation { +type CallFinding { standard: RegulationStandard! code: RegulationCode! reason: String! @@ -408,6 +409,14 @@ input ClusterConfigurationInput { clusterFormSettings: ClusterFormSettingsInput } +type ClusterFinding { + cluster: Cluster! + reason: String! + reasonId: String! + intentsOperatorState: IntentsOperatorState + relatedServices: [Service!] +} + type ClusterFormSettings { certificateProvider: CertificateProvider! enforcement: Boolean! @@ -418,13 +427,6 @@ input ClusterFormSettingsInput { enforcement: Boolean! } -type ClusterViolation { - cluster: Cluster! - reason: String! - intentsOperatorState: IntentsOperatorState - relatedServices: [Service!] -} - input Component { componentType: TelemetryComponentType! componentInstanceId: ID! @@ -485,8 +487,17 @@ input DNSIPPairInput { } type Dashboard { - findings: [Finding!]! - summary: FindingsSummary! + findings: [FindingSummary!]! + summary: DashboardData! +} + +type DashboardData { + findingsCount: Int! + nonCompliantClusters: Fraction! + nonCompliantWorkloads: Fraction! + overallCompliance: Fraction! + nonCompliantStandards: Fraction! + nonCompliantControls: Fraction! } type DatabaseConfig { @@ -696,27 +707,18 @@ type FeatureFlags { isCloudSecurityEnabled: Boolean } -type Finding { +type FindingSummary { standard: RegulationStandard! codeLabel: String! code: RegulationCode! severity: Severity! description: String! validationDescription: String - clusterViolations: [ClusterViolation!]! - serviceViolations: [ServiceViolation!]! + clusterFindings: [ClusterFinding!]! + serviceFindings: [ServiceFinding!]! clusterCount: Int! serviceCount: Int! - requirements: [Finding!] -} - -type FindingsSummary { - violations: Int! - nonCompliantClusters: Fraction! - nonCompliantWorkloads: Fraction! - overallCompliance: Fraction! - nonCompliantStandards: Fraction! - nonCompliantControls: Fraction! + requirements: [FindingSummary!] } """The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).""" @@ -890,6 +892,7 @@ input InputAccessGraphFilter { environmentIds: InputIDFilterValue lastSeen: InputTimeFilterValue featureFlags: InputFeatureFlags + includeOnlyClientsMatchingFilter: Boolean } """ Access log filter """ @@ -1938,7 +1941,7 @@ type Query { findings( filter: InputFindingFilter tree: Boolean - ): [Finding!]! + ): [FindingSummary!]! dashboard( filter: InputFindingFilter tree: Boolean @@ -2027,11 +2030,13 @@ type Query { namespaceId: ID name: String filter: InputServiceFilter + featureFlags: InputFeatureFlags ): [Service!]! """Paginate services""" paginateServices( filter: InputServiceFilter pagination: PaginationInput + featureFlags: InputFeatureFlags ): ServicesResponse! """Get service by filters""" oneService( @@ -2168,6 +2173,8 @@ enum ServerProtectionStatusReason { PROTECTED_BY_DATABASE_INTEGRATION PROTECTED_BY_AWS_IAM_INTEGRATION PROTECTED_BY_INTERNET_INTENTS + SERVER_EXTERNAL_ACCESS_POLICY_CREATED + EXTERNALLY_MANAGED_POLICY_WORKLOAD } enum ServerProtectionStatusVerdict { @@ -2239,6 +2246,13 @@ enum ServiceExternalTrafficPolicy { LOCAL } +type ServiceFinding { + service: Service! + reason: String! + reasonId: String! + callsFindings: [AccessGraphEdge!] +} + input ServiceIdentityInput { name: String! namespace: String! @@ -2270,12 +2284,6 @@ enum ServiceType { DETECTED_CLOUD_SERVER } -type ServiceViolation { - service: Service! - reason: String! - violatedCalls: [AccessGraphEdge!] -} - type ServicesResponse { data: [Service!]! meta: PaginationMeta From 45dbeebbf01944b7094dfbc0b273e4c07c2ba0d7 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Mon, 25 Nov 2024 17:07:01 +0200 Subject: [PATCH 04/15] temp --- .../iampolicyagents/azurepolicyagent/agent.go | 100 ++++++++++++------ src/shared/azureagent/customroles.go | 5 +- src/shared/azureagent/roleassignments.go | 8 ++ 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go index 0f18dc4f9..a66411079 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go @@ -11,6 +11,7 @@ import ( "github.com/otterize/intents-operator/src/shared/azureagent" "github.com/otterize/intents-operator/src/shared/errors" "github.com/samber/lo" + "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" "regexp" "strings" @@ -58,22 +59,22 @@ func (a *Agent) AddRolePolicyFromIntents(ctx context.Context, namespace string, return errors.Wrap(err) } - // Custom roles - azureCustomRolesIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { - hasCustomRoles := intent.Azure != nil && (len(intent.Azure.Actions) > 0 || len(intent.Azure.DataActions) > 0) - return hasCustomRoles && len(intent.Azure.Roles) == 0 - }) - if err := a.ensureCustomRolesForIntents(ctx, userAssignedIdentity, azureCustomRolesIntents); err != nil { - return errors.Wrap(err) - } - - // Backwards compatibility for role assignments - azureRBACIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { - return intent.Azure != nil && len(intent.Azure.Roles) > 0 - }) - if err := a.ensureRoleAssignmentsForIntents(ctx, userAssignedIdentity, azureRBACIntents); err != nil { - return errors.Wrap(err) - } + //// Custom roles + //azureCustomRolesIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { + // hasCustomRoles := intent.Azure != nil && (len(intent.Azure.Actions) > 0 || len(intent.Azure.DataActions) > 0) + // return hasCustomRoles && len(intent.Azure.Roles) == 0 + //}) + //if err := a.ensureCustomRolesForIntents(ctx, userAssignedIdentity, azureCustomRolesIntents); err != nil { + // return errors.Wrap(err) + //} + // + //// Backwards compatibility for role assignments + //azureRBACIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { + // return intent.Azure != nil && len(intent.Azure.Roles) > 0 + //}) + //if err := a.ensureRoleAssignmentsForIntents(ctx, userAssignedIdentity, azureRBACIntents); err != nil { + // return errors.Wrap(err) + //} // Key Vault permissions azureKeyVaultIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { @@ -94,7 +95,7 @@ func (a *Agent) ensureRoleAssignmentsForIntents(ctx context.Context, userAssigne // Filter out assignments on custom roles existingRoleAssignments = lo.Filter(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment, _ int) bool { - return strings.HasPrefix(*roleAssignment.Properties.RoleDefinitionID, "/providers/Microsoft.Authorization/roleDefinitions") + return !a.IsCustomRoleAssignment(roleAssignment) }) existingRoleAssignmentsByScope := lo.GroupBy(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment) string { @@ -173,6 +174,11 @@ func (a *Agent) deleteRoleAssignmentsWithUnexpectedScopes(ctx context.Context, e for _, roleAssignment := range existingRoleAssignments { scope := *roleAssignment.Properties.Scope if !expectedScopesSet.Contains(scope) { + // Handle default role assignments + if strings.HasPrefix(scope, "/subscriptions/") { + + } + if err := a.DeleteRoleAssignment(ctx, roleAssignment); err != nil { return errors.Wrap(err) } @@ -318,27 +324,59 @@ func (a *Agent) vaultAccessPolicyEntryFromIntent(userAssignedIdentity armmsi.Ide } func (a *Agent) ensureCustomRolesForIntents(ctx context.Context, userAssignedIdentity armmsi.Identity, intents []otterizev2alpha1.Target) error { + existingRoleAssignments, err := a.ListRoleAssignments(ctx, userAssignedIdentity) + if err != nil { + return errors.Wrap(err) + } + + // Filter out assignments on predefined roles + existingRoleAssignments = lo.Filter(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment, _ int) bool { + return a.IsCustomRoleAssignment(roleAssignment) + }) + + existingRoleAssignmentsByScope := lo.GroupBy(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment) string { + return *roleAssignment.Properties.Scope + }) + + logrus.Debug("existingRoleAssignmentsByScope", existingRoleAssignmentsByScope) + + var expectedScopes []string for _, intent := range intents { scope, err := a.getIntentScope(intent) if err != nil { return errors.Wrap(err) } - actions := intent.Azure.Actions - dataActions := intent.Azure.DataActions + expectedScopes = append(expectedScopes, scope) - customRoleName := a.GenerateCustomRoleName(userAssignedIdentity, scope) - role, found := a.FindCustomRoleByName(ctx, customRoleName) - if found { - err = a.UpdateCustomRole(ctx, role, actions, dataActions) - if err != nil { - return errors.Wrap(err) - } - } else { - err = a.CreateCustomRole(ctx, scope, userAssignedIdentity, actions, dataActions) - if err != nil { - return errors.Wrap(err) - } + err = a.ensureCustomRoleForIntent(ctx, userAssignedIdentity, scope, intent) + if err != nil { + return errors.Wrap(err) + } + } + + if err := a.deleteRoleAssignmentsWithUnexpectedScopes(ctx, expectedScopes, existingRoleAssignments); err != nil { + return errors.Wrap(err) + } + + return nil +} + +func (a *Agent) ensureCustomRoleForIntent(ctx context.Context, userAssignedIdentity armmsi.Identity, scope string, intent otterizev2alpha1.Target) error { + actions := intent.Azure.Actions + dataActions := intent.Azure.DataActions + + customRoleName := a.GenerateCustomRoleName(userAssignedIdentity, scope) + role, found := a.FindCustomRoleByName(ctx, customRoleName) + if found { + err := a.UpdateCustomRole(ctx, role, actions, dataActions) + if err != nil { + return errors.Wrap(err) + } + } else { + err := a.CreateCustomRole(ctx, scope, userAssignedIdentity, actions, dataActions) + if err != nil { + return errors.Wrap(err) } } diff --git a/src/shared/azureagent/customroles.go b/src/shared/azureagent/customroles.go index 5022c48eb..4486f708b 100644 --- a/src/shared/azureagent/customroles.go +++ b/src/shared/azureagent/customroles.go @@ -14,6 +14,9 @@ import ( ) const ( + // OtrzCustomRolePrefix is the prefix used for custom roles created by the Otterize agent + OtrzCustomRolePrefix = "ocr" + // maxRoleNameLength rules: 3-512 characters maxRoleNameLength = 200 ) @@ -37,7 +40,7 @@ func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.I return to.Ptr(string(action)) }) - id := uuid.NewString() + id := fmt.Sprintf("%s-%s", OtrzCustomRolePrefix, uuid.NewString()) name := a.GenerateCustomRoleName(uai, scope) description := fmt.Sprintf("Otterize managed custom role for uai [%s] with permissions for scope [%s]", *uai.Name, scope) diff --git a/src/shared/azureagent/roleassignments.go b/src/shared/azureagent/roleassignments.go index 94ce0c988..733645539 100644 --- a/src/shared/azureagent/roleassignments.go +++ b/src/shared/azureagent/roleassignments.go @@ -9,8 +9,16 @@ import ( "github.com/otterize/intents-operator/src/shared/errors" "github.com/samber/lo" "github.com/sirupsen/logrus" + "strings" ) +// IsCustomRoleAssignment checks if the given role assignment is a custom role assignment +// Custom role assignments start with: /subscriptions//providers/Microsoft.Authorization/ +// Built-in role assignments start with: /providers/Microsoft.Authorization/roleDefinitions +func (a *Agent) IsCustomRoleAssignment(roleAssignment armauthorization.RoleAssignment) bool { + return !strings.HasPrefix(*roleAssignment.Properties.RoleDefinitionID, "/providers/Microsoft.Authorization/roleDefinitions") +} + func (a *Agent) CreateRoleAssignment(ctx context.Context, scope string, userAssignedIdentity armmsi.Identity, roleDefinition armauthorization.RoleDefinition) error { roleAssignmentName := uuid.NewString() roleAssignment, err := a.roleAssignmentsClient.Create( From ddc2d83ec19103cacd61c030f10eceff89dfca13 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Tue, 26 Nov 2024 12:21:29 +0200 Subject: [PATCH 05/15] temp --- .../iampolicyagents/azurepolicyagent/agent.go | 43 ++++++++----------- src/shared/azureagent/azureclients.go | 1 + src/shared/azureagent/customroles.go | 42 +++++++++++++++--- src/shared/azureagent/roleassignments.go | 36 +++++++++++++--- 4 files changed, 87 insertions(+), 35 deletions(-) diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go index a66411079..45af79980 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go @@ -55,26 +55,26 @@ func (a *Agent) getIntentScope(intent otterizev2alpha1.Target) (string, error) { func (a *Agent) AddRolePolicyFromIntents(ctx context.Context, namespace string, _ string, intentsServiceName string, intents []otterizev2alpha1.Target, _ corev1.Pod) error { userAssignedIdentity, err := a.FindUserAssignedIdentity(ctx, namespace, intentsServiceName) - if err != nil { + if err == nil { return errors.Wrap(err) } - //// Custom roles - //azureCustomRolesIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { - // hasCustomRoles := intent.Azure != nil && (len(intent.Azure.Actions) > 0 || len(intent.Azure.DataActions) > 0) - // return hasCustomRoles && len(intent.Azure.Roles) == 0 - //}) - //if err := a.ensureCustomRolesForIntents(ctx, userAssignedIdentity, azureCustomRolesIntents); err != nil { - // return errors.Wrap(err) - //} - // - //// Backwards compatibility for role assignments - //azureRBACIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { - // return intent.Azure != nil && len(intent.Azure.Roles) > 0 - //}) - //if err := a.ensureRoleAssignmentsForIntents(ctx, userAssignedIdentity, azureRBACIntents); err != nil { - // return errors.Wrap(err) - //} + // Custom roles + azureCustomRolesIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { + hasCustomRoles := intent.Azure != nil && (len(intent.Azure.Actions) > 0 || len(intent.Azure.DataActions) > 0) + return hasCustomRoles && len(intent.Azure.Roles) == 0 + }) + if err := a.ensureCustomRolesForIntents(ctx, userAssignedIdentity, azureCustomRolesIntents); err != nil { + return errors.Wrap(err) + } + + // Backwards compatibility for role assignments + azureRBACIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { + return intent.Azure != nil && len(intent.Azure.Roles) > 0 + }) + if err := a.ensureRoleAssignmentsForIntents(ctx, userAssignedIdentity, azureRBACIntents); err != nil { + return errors.Wrap(err) + } // Key Vault permissions azureKeyVaultIntents := lo.Filter(intents, func(intent otterizev2alpha1.Target, _ int) bool { @@ -93,7 +93,7 @@ func (a *Agent) ensureRoleAssignmentsForIntents(ctx context.Context, userAssigne return errors.Wrap(err) } - // Filter out assignments on custom roles + // Filter out assignments on built-in roles existingRoleAssignments = lo.Filter(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment, _ int) bool { return !a.IsCustomRoleAssignment(roleAssignment) }) @@ -139,7 +139,7 @@ func (a *Agent) ensureRoleAssignmentsForIntent(ctx context.Context, scope string roleDefinition := roleDefinitionsByName[roleName] roleDefinitionID := *roleDefinition.ID if !existingRoleDefinitionIDs.Contains(roleDefinitionID) { - if err := a.CreateRoleAssignment(ctx, scope, userAssignedIdentity, roleDefinition); err != nil { + if err := a.CreateRoleAssignment(ctx, scope, userAssignedIdentity, roleDefinition, nil); err != nil { return errors.Wrap(err) } } @@ -174,11 +174,6 @@ func (a *Agent) deleteRoleAssignmentsWithUnexpectedScopes(ctx context.Context, e for _, roleAssignment := range existingRoleAssignments { scope := *roleAssignment.Properties.Scope if !expectedScopesSet.Contains(scope) { - // Handle default role assignments - if strings.HasPrefix(scope, "/subscriptions/") { - - } - if err := a.DeleteRoleAssignment(ctx, roleAssignment); err != nil { return errors.Wrap(err) } diff --git a/src/shared/azureagent/azureclients.go b/src/shared/azureagent/azureclients.go index 3f65709c8..c95a71e39 100644 --- a/src/shared/azureagent/azureclients.go +++ b/src/shared/azureagent/azureclients.go @@ -36,6 +36,7 @@ type AzureARMMSIFederatedIdentityCredentialsClient interface { } type AzureARMAuthorizationRoleDefinitionsClient interface { + Delete(ctx context.Context, scope string, roleDefinitionID string, options *armauthorization.RoleDefinitionsClientDeleteOptions) (armauthorization.RoleDefinitionsClientDeleteResponse, error) CreateOrUpdate(ctx context.Context, scope string, roleDefinitionID string, roleDefinition armauthorization.RoleDefinition, options *armauthorization.RoleDefinitionsClientCreateOrUpdateOptions) (armauthorization.RoleDefinitionsClientCreateOrUpdateResponse, error) NewListPager(scope string, options *armauthorization.RoleDefinitionsClientListOptions) *runtime.Pager[armauthorization.RoleDefinitionsClientListResponse] } diff --git a/src/shared/azureagent/customroles.go b/src/shared/azureagent/customroles.go index 4486f708b..ac9c84dd0 100644 --- a/src/shared/azureagent/customroles.go +++ b/src/shared/azureagent/customroles.go @@ -14,11 +14,10 @@ import ( ) const ( - // OtrzCustomRolePrefix is the prefix used for custom roles created by the Otterize agent - OtrzCustomRolePrefix = "ocr" - // maxRoleNameLength rules: 3-512 characters maxRoleNameLength = 200 + + otterizeCustomRoleTag = "ocr" ) func (a *Agent) getCustomRoleScope() string { @@ -40,7 +39,7 @@ func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.I return to.Ptr(string(action)) }) - id := fmt.Sprintf("%s-%s", OtrzCustomRolePrefix, uuid.NewString()) + id := uuid.NewString() name := a.GenerateCustomRoleName(uai, scope) description := fmt.Sprintf("Otterize managed custom role for uai [%s] with permissions for scope [%s]", *uai.Name, scope) @@ -65,7 +64,7 @@ func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.I } // create a role assignment for the custom role - err = a.CreateRoleAssignment(ctx, scope, uai, resp.RoleDefinition) + err = a.CreateRoleAssignment(ctx, scope, uai, resp.RoleDefinition, to.Ptr(otterizeCustomRoleTag)) if err != nil { return errors.Wrap(err) } @@ -115,3 +114,36 @@ func (a *Agent) FindCustomRoleByName(ctx context.Context, name string) (*armauth return nil, false } + +func (a *Agent) ListCustomRoleDefinitions(ctx context.Context) ([]armauthorization.RoleDefinition, error) { + scope := a.getCustomRoleScope() + + var roles []armauthorization.RoleDefinition + pager := a.roleDefinitionsClient.NewListPager(scope, nil) + + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, errors.Wrap(err) + } + + for _, role := range page.Value { + if *role.Properties.RoleType == "CustomRole" { + roles = append(roles, *role) + } + } + } + + return roles, nil +} + +func (a *Agent) DeleteCustomRole(ctx context.Context, role *armauthorization.RoleDefinition) error { + scope := a.getCustomRoleScope() + + _, err := a.roleDefinitionsClient.Delete(ctx, scope, *role.Name, nil) + if err != nil { + return errors.Wrap(err) + } + + return nil +} diff --git a/src/shared/azureagent/roleassignments.go b/src/shared/azureagent/roleassignments.go index 733645539..3221aec3f 100644 --- a/src/shared/azureagent/roleassignments.go +++ b/src/shared/azureagent/roleassignments.go @@ -9,17 +9,13 @@ import ( "github.com/otterize/intents-operator/src/shared/errors" "github.com/samber/lo" "github.com/sirupsen/logrus" - "strings" ) -// IsCustomRoleAssignment checks if the given role assignment is a custom role assignment -// Custom role assignments start with: /subscriptions//providers/Microsoft.Authorization/ -// Built-in role assignments start with: /providers/Microsoft.Authorization/roleDefinitions func (a *Agent) IsCustomRoleAssignment(roleAssignment armauthorization.RoleAssignment) bool { - return !strings.HasPrefix(*roleAssignment.Properties.RoleDefinitionID, "/providers/Microsoft.Authorization/roleDefinitions") + return *roleAssignment.Properties.Description == otterizeCustomRoleTag } -func (a *Agent) CreateRoleAssignment(ctx context.Context, scope string, userAssignedIdentity armmsi.Identity, roleDefinition armauthorization.RoleDefinition) error { +func (a *Agent) CreateRoleAssignment(ctx context.Context, scope string, userAssignedIdentity armmsi.Identity, roleDefinition armauthorization.RoleDefinition, desc *string) error { roleAssignmentName := uuid.NewString() roleAssignment, err := a.roleAssignmentsClient.Create( ctx, @@ -30,6 +26,7 @@ func (a *Agent) CreateRoleAssignment(ctx context.Context, scope string, userAssi PrincipalID: userAssignedIdentity.Properties.PrincipalID, PrincipalType: lo.ToPtr(armauthorization.PrincipalTypeServicePrincipal), RoleDefinitionID: roleDefinition.ID, + Description: desc, }, }, nil) @@ -100,3 +97,30 @@ func (a *Agent) FindRoleDefinitionByName(ctx context.Context, scope string, role return roleDefinitionsByName, nil } + +func (a *Agent) ListCustomRoleAssignments(ctx context.Context, userAssignedIdentity armmsi.Identity) ([]armauthorization.RoleAssignment, error) { + roleAssignments, err := a.ListRoleAssignments(ctx, userAssignedIdentity) + if err != nil { + return nil, errors.Wrap(err) + } + + roleDefinitions, err := a.ListCustomRoleDefinitions(ctx) + if err != nil { + return nil, errors.Wrap(err) + } + + roleDefinitionsByName := map[string]armauthorization.RoleDefinition{} + for _, roleDef := range roleDefinitions { + roleDefinitionsByName[*roleDef.Properties.RoleName] = roleDef + } + + var customRoleAssignments []armauthorization.RoleAssignment + for _, roleAssignment := range roleAssignments { + roleDef, exists := roleDefinitionsByName[*roleAssignment.Properties.RoleDefinitionID] + if exists && *roleDef.Properties.RoleType == "CustomRole" { + customRoleAssignments = append(customRoleAssignments, roleAssignment) + } + } + + return customRoleAssignments, nil +} From 97736dafd4fe0fb58a3aa670171b3052e6e85476 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Tue, 26 Nov 2024 12:23:42 +0200 Subject: [PATCH 06/15] go generate --- src/shared/azureagent/mocks/azureclients.go | 15 +++++++++++++++ .../otterizecloud/graphqlclient/schema.graphql | 7 +++++++ .../telemetries/telemetriesgql/schema.graphql | 7 +++++++ 3 files changed, 29 insertions(+) diff --git a/src/shared/azureagent/mocks/azureclients.go b/src/shared/azureagent/mocks/azureclients.go index e9f244344..d6e6f9e3b 100644 --- a/src/shared/azureagent/mocks/azureclients.go +++ b/src/shared/azureagent/mocks/azureclients.go @@ -306,6 +306,21 @@ func (mr *MockAzureARMAuthorizationRoleDefinitionsClientMockRecorder) CreateOrUp return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOrUpdate", reflect.TypeOf((*MockAzureARMAuthorizationRoleDefinitionsClient)(nil).CreateOrUpdate), ctx, scope, roleDefinitionID, roleDefinition, options) } +// Delete mocks base method. +func (m *MockAzureARMAuthorizationRoleDefinitionsClient) Delete(ctx context.Context, scope, roleDefinitionID string, options *armauthorization.RoleDefinitionsClientDeleteOptions) (armauthorization.RoleDefinitionsClientDeleteResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Delete", ctx, scope, roleDefinitionID, options) + ret0, _ := ret[0].(armauthorization.RoleDefinitionsClientDeleteResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Delete indicates an expected call of Delete. +func (mr *MockAzureARMAuthorizationRoleDefinitionsClientMockRecorder) Delete(ctx, scope, roleDefinitionID, options interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockAzureARMAuthorizationRoleDefinitionsClient)(nil).Delete), ctx, scope, roleDefinitionID, options) +} + // NewListPager mocks base method. func (m *MockAzureARMAuthorizationRoleDefinitionsClient) NewListPager(scope string, options *armauthorization.RoleDefinitionsClientListOptions) *runtime.Pager[armauthorization.RoleDefinitionsClientListResponse] { m.ctrl.T.Helper() diff --git a/src/shared/otterizecloud/graphqlclient/schema.graphql b/src/shared/otterizecloud/graphqlclient/schema.graphql index c8d65ca1a..526e2d931 100644 --- a/src/shared/otterizecloud/graphqlclient/schema.graphql +++ b/src/shared/otterizecloud/graphqlclient/schema.graphql @@ -1737,6 +1737,11 @@ type Mutation { id: ID! userId: ID! ): ID! +"""Ignore domain for organization""" + ignoreOrganizationDomain( + id: ID! + domain: String! + ): Organization! reportProtectedServicesSnapshot( namespace: String! services: [ProtectedServiceInput!]! @@ -1853,11 +1858,13 @@ type Organization { type OrganizationSettings { domains: [String!] enforcedRegulations: [String!] + ignoredCloudDomains: [String!] } input OrganizationSettingsInput { domains: [String!] enforcedRegulations: [String] + ignoredCloudDomains: [String!] } input PaginationInput { diff --git a/src/shared/telemetries/telemetriesgql/schema.graphql b/src/shared/telemetries/telemetriesgql/schema.graphql index c8d65ca1a..526e2d931 100644 --- a/src/shared/telemetries/telemetriesgql/schema.graphql +++ b/src/shared/telemetries/telemetriesgql/schema.graphql @@ -1737,6 +1737,11 @@ type Mutation { id: ID! userId: ID! ): ID! +"""Ignore domain for organization""" + ignoreOrganizationDomain( + id: ID! + domain: String! + ): Organization! reportProtectedServicesSnapshot( namespace: String! services: [ProtectedServiceInput!]! @@ -1853,11 +1858,13 @@ type Organization { type OrganizationSettings { domains: [String!] enforcedRegulations: [String!] + ignoredCloudDomains: [String!] } input OrganizationSettingsInput { domains: [String!] enforcedRegulations: [String] + ignoredCloudDomains: [String!] } input PaginationInput { From 81feb47e3e3d21c0da617d18574644fe386511ed Mon Sep 17 00:00:00 2001 From: davidrobert Date: Tue, 26 Nov 2024 14:23:08 +0200 Subject: [PATCH 07/15] working version --- .../iampolicyagents/azurepolicyagent/agent.go | 22 ++++++---- src/shared/azureagent/customroles.go | 26 +---------- src/shared/azureagent/roleassignments.go | 43 +++++++------------ 3 files changed, 31 insertions(+), 60 deletions(-) diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go index 45af79980..5017573d8 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go @@ -11,20 +11,22 @@ import ( "github.com/otterize/intents-operator/src/shared/azureagent" "github.com/otterize/intents-operator/src/shared/errors" "github.com/samber/lo" - "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" "regexp" "strings" + "sync" ) var KeyVaultNameRegex = regexp.MustCompile(`^/subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft.KeyVault/vaults/([^/]+)$`) type Agent struct { *azureagent.Agent + roleMutex sync.Mutex + assignmentMutex sync.Mutex } func NewAzurePolicyAgent(azureAgent *azureagent.Agent) *Agent { - return &Agent{azureAgent} + return &Agent{azureAgent, sync.Mutex{}, sync.Mutex{}} } func (a *Agent) IntentType() otterizev2alpha1.IntentType { @@ -55,7 +57,7 @@ func (a *Agent) getIntentScope(intent otterizev2alpha1.Target) (string, error) { func (a *Agent) AddRolePolicyFromIntents(ctx context.Context, namespace string, _ string, intentsServiceName string, intents []otterizev2alpha1.Target, _ corev1.Pod) error { userAssignedIdentity, err := a.FindUserAssignedIdentity(ctx, namespace, intentsServiceName) - if err == nil { + if err != nil { return errors.Wrap(err) } @@ -88,6 +90,10 @@ func (a *Agent) AddRolePolicyFromIntents(ctx context.Context, namespace string, } func (a *Agent) ensureRoleAssignmentsForIntents(ctx context.Context, userAssignedIdentity armmsi.Identity, intents []otterizev2alpha1.Target) error { + // Lock the agent to ensure that no other goroutine is modifying the assignments + a.assignmentMutex.Lock() + defer a.assignmentMutex.Unlock() + existingRoleAssignments, err := a.ListRoleAssignments(ctx, userAssignedIdentity) if err != nil { return errors.Wrap(err) @@ -319,6 +325,10 @@ func (a *Agent) vaultAccessPolicyEntryFromIntent(userAssignedIdentity armmsi.Ide } func (a *Agent) ensureCustomRolesForIntents(ctx context.Context, userAssignedIdentity armmsi.Identity, intents []otterizev2alpha1.Target) error { + // Lock the agent to ensure that no other goroutine is modifying the custom roles in parallel + a.roleMutex.Lock() + defer a.roleMutex.Unlock() + existingRoleAssignments, err := a.ListRoleAssignments(ctx, userAssignedIdentity) if err != nil { return errors.Wrap(err) @@ -329,12 +339,6 @@ func (a *Agent) ensureCustomRolesForIntents(ctx context.Context, userAssignedIde return a.IsCustomRoleAssignment(roleAssignment) }) - existingRoleAssignmentsByScope := lo.GroupBy(existingRoleAssignments, func(roleAssignment armauthorization.RoleAssignment) string { - return *roleAssignment.Properties.Scope - }) - - logrus.Debug("existingRoleAssignmentsByScope", existingRoleAssignmentsByScope) - var expectedScopes []string for _, intent := range intents { scope, err := a.getIntentScope(intent) diff --git a/src/shared/azureagent/customroles.go b/src/shared/azureagent/customroles.go index ac9c84dd0..fec95276d 100644 --- a/src/shared/azureagent/customroles.go +++ b/src/shared/azureagent/customroles.go @@ -115,32 +115,10 @@ func (a *Agent) FindCustomRoleByName(ctx context.Context, name string) (*armauth return nil, false } -func (a *Agent) ListCustomRoleDefinitions(ctx context.Context) ([]armauthorization.RoleDefinition, error) { +func (a *Agent) DeleteCustomRole(ctx context.Context, roleDefinitionID string) error { scope := a.getCustomRoleScope() - var roles []armauthorization.RoleDefinition - pager := a.roleDefinitionsClient.NewListPager(scope, nil) - - for pager.More() { - page, err := pager.NextPage(ctx) - if err != nil { - return nil, errors.Wrap(err) - } - - for _, role := range page.Value { - if *role.Properties.RoleType == "CustomRole" { - roles = append(roles, *role) - } - } - } - - return roles, nil -} - -func (a *Agent) DeleteCustomRole(ctx context.Context, role *armauthorization.RoleDefinition) error { - scope := a.getCustomRoleScope() - - _, err := a.roleDefinitionsClient.Delete(ctx, scope, *role.Name, nil) + _, err := a.roleDefinitionsClient.Delete(ctx, scope, roleDefinitionID, nil) if err != nil { return errors.Wrap(err) } diff --git a/src/shared/azureagent/roleassignments.go b/src/shared/azureagent/roleassignments.go index 3221aec3f..748791fb5 100644 --- a/src/shared/azureagent/roleassignments.go +++ b/src/shared/azureagent/roleassignments.go @@ -9,9 +9,13 @@ import ( "github.com/otterize/intents-operator/src/shared/errors" "github.com/samber/lo" "github.com/sirupsen/logrus" + "strings" ) func (a *Agent) IsCustomRoleAssignment(roleAssignment armauthorization.RoleAssignment) bool { + if roleAssignment.Properties.Description == nil { + return false + } return *roleAssignment.Properties.Description == otterizeCustomRoleTag } @@ -49,6 +53,18 @@ func (a *Agent) DeleteRoleAssignment(ctx context.Context, roleAssignment armauth if err != nil { return errors.Wrap(err) } + + // If the assignment is for a custom role, we also need to delete the role itself + if a.IsCustomRoleAssignment(roleAssignment) { + // The role id is the last hash of the assignments roleDefinitionID + fullID := *roleAssignment.Properties.RoleDefinitionID + roleDefinitionID := fullID[strings.LastIndex(fullID, "/")+1:] + + if err := a.DeleteCustomRole(ctx, roleDefinitionID); err != nil { + return errors.Wrap(err) + } + } + return nil } @@ -97,30 +113,3 @@ func (a *Agent) FindRoleDefinitionByName(ctx context.Context, scope string, role return roleDefinitionsByName, nil } - -func (a *Agent) ListCustomRoleAssignments(ctx context.Context, userAssignedIdentity armmsi.Identity) ([]armauthorization.RoleAssignment, error) { - roleAssignments, err := a.ListRoleAssignments(ctx, userAssignedIdentity) - if err != nil { - return nil, errors.Wrap(err) - } - - roleDefinitions, err := a.ListCustomRoleDefinitions(ctx) - if err != nil { - return nil, errors.Wrap(err) - } - - roleDefinitionsByName := map[string]armauthorization.RoleDefinition{} - for _, roleDef := range roleDefinitions { - roleDefinitionsByName[*roleDef.Properties.RoleName] = roleDef - } - - var customRoleAssignments []armauthorization.RoleAssignment - for _, roleAssignment := range roleAssignments { - roleDef, exists := roleDefinitionsByName[*roleAssignment.Properties.RoleDefinitionID] - if exists && *roleDef.Properties.RoleType == "CustomRole" { - customRoleAssignments = append(customRoleAssignments, roleAssignment) - } - } - - return customRoleAssignments, nil -} From e850614c6de979709ea4569facf17d8134191946 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Tue, 26 Nov 2024 14:29:27 +0200 Subject: [PATCH 08/15] fix go vet --- .../iampolicyagents/azurepolicyagent/agent_keyvault_test.go | 3 +++ .../iam/iampolicyagents/azurepolicyagent/agent_test.go | 3 +++ src/shared/azureagent/customroles.go | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go index 213274a66..9416a3c15 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go @@ -15,6 +15,7 @@ import ( "go.uber.org/mock/gomock" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sync" "testing" ) @@ -64,6 +65,8 @@ func (s *AzureAgentPoliciesKeyVaultSuite) SetupTest() { s.mockRoleAssignmentsClient, s.mockVaultsClient, ), + sync.Mutex{}, + sync.Mutex{}, } } diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_test.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_test.go index d1d68a850..cf6ccf3d6 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_test.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_test.go @@ -4,6 +4,7 @@ import ( otterizev2alpha1 "github.com/otterize/intents-operator/src/operator/api/v2alpha1" "github.com/otterize/intents-operator/src/shared/azureagent" "github.com/stretchr/testify/suite" + "sync" "testing" ) @@ -35,6 +36,8 @@ func (s *AzureAgentPoliciesSuite) SetupTest() { ResourceGroup: testResourceGroup, }, }, + sync.Mutex{}, + sync.Mutex{}, } } diff --git a/src/shared/azureagent/customroles.go b/src/shared/azureagent/customroles.go index fec95276d..a7a32ef53 100644 --- a/src/shared/azureagent/customroles.go +++ b/src/shared/azureagent/customroles.go @@ -17,7 +17,7 @@ const ( // maxRoleNameLength rules: 3-512 characters maxRoleNameLength = 200 - otterizeCustomRoleTag = "ocr" + otterizeCustomRoleTag = "OtterizeCustomRole" ) func (a *Agent) getCustomRoleScope() string { From cfbdf7d3aa94d6038b3076bf5a527bd1415a244a Mon Sep 17 00:00:00 2001 From: davidrobert Date: Tue, 26 Nov 2024 14:49:28 +0200 Subject: [PATCH 09/15] test fix --- .../iampolicyagents/azurepolicyagent/agent_keyvault_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go index 9416a3c15..e9380c1cd 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go @@ -222,7 +222,11 @@ func (s *AzureAgentPoliciesKeyVaultSuite) TestAddRolePolicyFromIntents_AzureKeyV clientId := uuid.NewString() s.expectGetUserAssignedIdentityReturnsClientID(clientId) + + // Two calls - one from custom roles and one from backwards compatibility to built-in roles + s.expectListRoleAssignmentsReturnsEmpty() s.expectListRoleAssignmentsReturnsEmpty() + s.expectListKeyVaultsReturnsNames(testKeyVaultName) for _, policy := range testCase.ExisingAccessPolicy { From c7665d776bf6dd35997e52b1af44065d077a406d Mon Sep 17 00:00:00 2001 From: davidrobert Date: Tue, 26 Nov 2024 19:26:12 +0200 Subject: [PATCH 10/15] add tests --- .../agent_customroles_test.go | 216 ++++++++++++++++++ .../azurepolicyagent/agent_keyvault_test.go | 1 - src/shared/azureagent/roleassignments.go | 6 +- 3 files changed, 217 insertions(+), 6 deletions(-) create mode 100644 src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go new file mode 100644 index 000000000..f4059d840 --- /dev/null +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go @@ -0,0 +1,216 @@ +package azurepolicyagent + +import ( + "context" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" + "github.com/google/uuid" + otterizev2alpha1 "github.com/otterize/intents-operator/src/operator/api/v2alpha1" + "github.com/otterize/intents-operator/src/shared/azureagent" + mock_azureagent "github.com/otterize/intents-operator/src/shared/azureagent/mocks" + "github.com/stretchr/testify/suite" + "go.uber.org/mock/gomock" + corev1 "k8s.io/api/core/v1" + "sync" + "testing" +) + +type AzureCustomRoleTestCase struct { + Name string + Actions []otterizev2alpha1.AzureAction + DataActions []otterizev2alpha1.AzureDataAction + ExisingCustomRoles []*armauthorization.RoleDefinition + UpdateExpected bool + ShouldCreateAssignment bool +} + +type AzureAgentPoliciesCustomRolesSuite struct { + suite.Suite + + mockSubscriptionsClient *mock_azureagent.MockAzureARMSubscriptionsClient + mockResourceGroupsClient *mock_azureagent.MockAzureARMResourcesResourceGroupsClient + mockManagedClustersClient *mock_azureagent.MockAzureARMContainerServiceManagedClustersClient + mockUserAssignedIdentitiesClient *mock_azureagent.MockAzureARMMSIUserAssignedIdentitiesClient + mockFederatedIdentityCredentialsClient *mock_azureagent.MockAzureARMMSIFederatedIdentityCredentialsClient + mockRoleDefinitionsClient *mock_azureagent.MockAzureARMAuthorizationRoleDefinitionsClient + mockRoleAssignmentsClient *mock_azureagent.MockAzureARMAuthorizationRoleAssignmentsClient + mockVaultsClient *mock_azureagent.MockAzureARMKeyVaultVaultsClient + + agent *Agent +} + +func (s *AzureAgentPoliciesCustomRolesSuite) expectListRoleDefinitionsReturnsEmpty(scope string, roles []*armauthorization.RoleDefinition) { + s.mockRoleDefinitionsClient.EXPECT().NewListPager(scope, gomock.Any()).Return(azureagent.NewListPager[armauthorization.RoleDefinitionsClientListResponse]( + armauthorization.RoleDefinitionsClientListResponse{ + RoleDefinitionListResult: armauthorization.RoleDefinitionListResult{ + Value: roles, + }, + }, + )) +} + +func (s *AzureAgentPoliciesCustomRolesSuite) expectCreateOrUpdateRoleDefinitionWriteRoleDefinition(customRoleDefinition *armauthorization.RoleDefinition) { + s.mockRoleDefinitionsClient.EXPECT().CreateOrUpdate(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn( + func(ctx context.Context, scope string, roleDefinitionID string, roleDefinition armauthorization.RoleDefinition, options *armauthorization.RoleDefinitionsClientCreateOrUpdateOptions) (armauthorization.RoleDefinitionsClientCreateOrUpdateResponse, error) { + *customRoleDefinition = roleDefinition + return armauthorization.RoleDefinitionsClientCreateOrUpdateResponse{ + RoleDefinition: roleDefinition, + }, nil + }, + ) +} + +func (s *AzureAgentPoliciesCustomRolesSuite) expectCreateRoleAssignmentReturnsEmpty() { + s.mockRoleAssignmentsClient.EXPECT().Create(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(armauthorization.RoleAssignmentsClientCreateResponse{ + RoleAssignment: armauthorization.RoleAssignment{ + Properties: &armauthorization.RoleAssignmentProperties{}, + }, + }, nil) +} + +func (s *AzureAgentPoliciesCustomRolesSuite) expectListRoleAssignmentsReturnsEmpty() { + s.mockRoleAssignmentsClient.EXPECT().NewListForSubscriptionPager(nil).Return(azureagent.NewListPager[armauthorization.RoleAssignmentsClientListForSubscriptionResponse]()) +} + +func (s *AzureAgentPoliciesCustomRolesSuite) expectGetUserAssignedIdentityReturnsClientID(clientId string) { + userAssignedIndentityName := s.agent.GenerateUserAssignedIdentityName(testNamespace, testIntentsServiceName) + s.mockUserAssignedIdentitiesClient.EXPECT().Get(gomock.Any(), testResourceGroup, userAssignedIndentityName, nil).Return( + armmsi.UserAssignedIdentitiesClientGetResponse{ + Identity: armmsi.Identity{ + Name: &userAssignedIndentityName, + Properties: &armmsi.UserAssignedIdentityProperties{ + ClientID: &clientId, + }, + }, + }, nil) +} + +func (s *AzureAgentPoliciesCustomRolesSuite) expectListKeyVaultsReturnsEmpty() { + s.mockVaultsClient.EXPECT().NewListByResourceGroupPager(testResourceGroup, nil).Return(azureagent.NewListPager[armkeyvault.VaultsClientListByResourceGroupResponse]( + armkeyvault.VaultsClientListByResourceGroupResponse{}, + )) +} + +func (s *AzureAgentPoliciesCustomRolesSuite) SetupTest() { + controller := gomock.NewController(s.T()) + s.mockSubscriptionsClient = mock_azureagent.NewMockAzureARMSubscriptionsClient(controller) + s.mockResourceGroupsClient = mock_azureagent.NewMockAzureARMResourcesResourceGroupsClient(controller) + s.mockManagedClustersClient = mock_azureagent.NewMockAzureARMContainerServiceManagedClustersClient(controller) + s.mockUserAssignedIdentitiesClient = mock_azureagent.NewMockAzureARMMSIUserAssignedIdentitiesClient(controller) + s.mockFederatedIdentityCredentialsClient = mock_azureagent.NewMockAzureARMMSIFederatedIdentityCredentialsClient(controller) + s.mockRoleDefinitionsClient = mock_azureagent.NewMockAzureARMAuthorizationRoleDefinitionsClient(controller) + s.mockRoleAssignmentsClient = mock_azureagent.NewMockAzureARMAuthorizationRoleAssignmentsClient(controller) + s.mockVaultsClient = mock_azureagent.NewMockAzureARMKeyVaultVaultsClient(controller) + + s.agent = &Agent{ + azureagent.NewAzureAgentFromClients( + azureagent.Config{ + SubscriptionID: testSubscriptionID, + ResourceGroup: testResourceGroup, + AKSClusterName: testAKSClusterName, + TenantID: testTenantID, + Location: testLocation, + AKSClusterOIDCIssuerURL: testOIDCIssuerURL, + }, + nil, + s.mockSubscriptionsClient, + s.mockResourceGroupsClient, + s.mockManagedClustersClient, + s.mockUserAssignedIdentitiesClient, + s.mockFederatedIdentityCredentialsClient, + s.mockRoleDefinitionsClient, + s.mockRoleAssignmentsClient, + s.mockVaultsClient, + ), + sync.Mutex{}, + sync.Mutex{}, + } +} + +var azureCustomRoleTestCases = []AzureCustomRoleTestCase{ + { + Name: "AddsNewPolicy", + Actions: []otterizev2alpha1.AzureAction{ + "Microsoft.Storage/storageAccounts/blobServices/containers/read", + }, + ExisingCustomRoles: nil, + UpdateExpected: true, + ShouldCreateAssignment: true, + }, + { + Name: "AddsNewPolicy", + Actions: []otterizev2alpha1.AzureAction{ + "Microsoft.Storage/storageAccounts/blobServices/containers/read", + "Microsoft.Storage/storageAccounts/blobServices/containers/write", + }, + ExisingCustomRoles: []*armauthorization.RoleDefinition{ + { + Name: to.Ptr("otterizeCustomRole"), + Properties: &armauthorization.RoleDefinitionProperties{ + Permissions: []*armauthorization.Permission{ + { + Actions: []*string{ + to.Ptr("Microsoft.Storage/storageAccounts/blobServices/containers/read"), + }, + }, + }, + }, + }, + }, + UpdateExpected: true, + ShouldCreateAssignment: false, + }, +} + +func (s *AzureAgentPoliciesCustomRolesSuite) TestAddRolePolicyFromIntents_CustomRoles() { + for _, testCase := range azureCustomRoleTestCases { + s.Run(testCase.Name, func() { + intents := []otterizev2alpha1.Target{ + { + Azure: &otterizev2alpha1.AzureTarget{ + Scope: "/providers/Microsoft.Storage/storageAccounts/test/blobServices/default/containers/container", + Actions: testCase.Actions, + DataActions: testCase.DataActions, + }, + }, + } + + customRoleScope := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", testSubscriptionID, testResourceGroup) + + clientId := uuid.NewString() + s.expectGetUserAssignedIdentityReturnsClientID(clientId) + + s.expectListKeyVaultsReturnsEmpty() + + // Two calls - one from custom roles and one from backwards compatibility to built-in roles + s.expectListRoleAssignmentsReturnsEmpty() + s.expectListRoleAssignmentsReturnsEmpty() + + // CustomRole related calls + s.expectListRoleDefinitionsReturnsEmpty(customRoleScope, testCase.ExisingCustomRoles) + if testCase.ShouldCreateAssignment { + s.expectCreateRoleAssignmentReturnsEmpty() + } + + // Make sure the custom role is created + var customRoleDefinition armauthorization.RoleDefinition + s.expectCreateOrUpdateRoleDefinitionWriteRoleDefinition(&customRoleDefinition) + + err := s.agent.AddRolePolicyFromIntents(context.Background(), testNamespace, testAccountName, testIntentsServiceName, intents, corev1.Pod{}) + s.NoError(err) + + if testCase.UpdateExpected { + s.Require().Len(customRoleDefinition.Properties.Permissions, 1) + s.Require().Len(customRoleDefinition.Properties.Permissions[0].Actions, len(testCase.Actions)) + s.Require().Len(customRoleDefinition.Properties.Permissions[0].DataActions, len(testCase.DataActions)) + } + }) + } +} + +func TestAzureAgentPoliciesCustomRolesSuite(t *testing.T) { + suite.Run(t, new(AzureAgentPoliciesCustomRolesSuite)) +} diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go index e9380c1cd..41e87cab9 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_keyvault_test.go @@ -119,7 +119,6 @@ func (s *AzureAgentPoliciesKeyVaultSuite) expectUpdateKeyVaultAccessPolicyWrites *updatedPolicy = parameters return armkeyvault.VaultsClientUpdateAccessPolicyResponse{}, nil }) - } type AzureKeyVaultPolicyTestCase struct { diff --git a/src/shared/azureagent/roleassignments.go b/src/shared/azureagent/roleassignments.go index 748791fb5..9d72dfeaa 100644 --- a/src/shared/azureagent/roleassignments.go +++ b/src/shared/azureagent/roleassignments.go @@ -21,7 +21,7 @@ func (a *Agent) IsCustomRoleAssignment(roleAssignment armauthorization.RoleAssig func (a *Agent) CreateRoleAssignment(ctx context.Context, scope string, userAssignedIdentity armmsi.Identity, roleDefinition armauthorization.RoleDefinition, desc *string) error { roleAssignmentName := uuid.NewString() - roleAssignment, err := a.roleAssignmentsClient.Create( + _, err := a.roleAssignmentsClient.Create( ctx, scope, roleAssignmentName, @@ -37,10 +37,6 @@ func (a *Agent) CreateRoleAssignment(ctx context.Context, scope string, userAssi if err != nil { return errors.Wrap(err) } - logrus.WithField("scope", *roleAssignment.Properties.Scope). - WithField("role", *roleAssignment.Properties.RoleDefinitionID). - WithField("assignment", *roleAssignment.Name). - Debug("role assignment created") return nil } From 57a9170154b322081d4f98f00b18eaeebec2113b Mon Sep 17 00:00:00 2001 From: davidrobert Date: Wed, 27 Nov 2024 12:34:06 +0200 Subject: [PATCH 11/15] backwards compatibility test --- .../agent_customroles_test.go | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go index f4059d840..81fad1241 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent_customroles_test.go @@ -2,7 +2,6 @@ package azurepolicyagent import ( "context" - "fmt" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" @@ -20,9 +19,10 @@ import ( type AzureCustomRoleTestCase struct { Name string + Roles []string Actions []otterizev2alpha1.AzureAction DataActions []otterizev2alpha1.AzureDataAction - ExisingCustomRoles []*armauthorization.RoleDefinition + ExisingRoles []*armauthorization.RoleDefinition UpdateExpected bool ShouldCreateAssignment bool } @@ -42,8 +42,8 @@ type AzureAgentPoliciesCustomRolesSuite struct { agent *Agent } -func (s *AzureAgentPoliciesCustomRolesSuite) expectListRoleDefinitionsReturnsEmpty(scope string, roles []*armauthorization.RoleDefinition) { - s.mockRoleDefinitionsClient.EXPECT().NewListPager(scope, gomock.Any()).Return(azureagent.NewListPager[armauthorization.RoleDefinitionsClientListResponse]( +func (s *AzureAgentPoliciesCustomRolesSuite) expectListRoleDefinitionsReturnsPager(roles []*armauthorization.RoleDefinition) { + s.mockRoleDefinitionsClient.EXPECT().NewListPager(gomock.Any(), gomock.Any()).Return(azureagent.NewListPager[armauthorization.RoleDefinitionsClientListResponse]( armauthorization.RoleDefinitionsClientListResponse{ RoleDefinitionListResult: armauthorization.RoleDefinitionListResult{ Value: roles, @@ -136,17 +136,21 @@ var azureCustomRoleTestCases = []AzureCustomRoleTestCase{ Actions: []otterizev2alpha1.AzureAction{ "Microsoft.Storage/storageAccounts/blobServices/containers/read", }, - ExisingCustomRoles: nil, + DataActions: []otterizev2alpha1.AzureDataAction{ + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read", + "Microsoft.Storage/storageAccounts/blobServices/containers/blobs/add/action", + }, + ExisingRoles: nil, UpdateExpected: true, ShouldCreateAssignment: true, }, { - Name: "AddsNewPolicy", + Name: "UpdatesExistingPolicy", Actions: []otterizev2alpha1.AzureAction{ "Microsoft.Storage/storageAccounts/blobServices/containers/read", "Microsoft.Storage/storageAccounts/blobServices/containers/write", }, - ExisingCustomRoles: []*armauthorization.RoleDefinition{ + ExisingRoles: []*armauthorization.RoleDefinition{ { Name: to.Ptr("otterizeCustomRole"), Properties: &armauthorization.RoleDefinitionProperties{ @@ -163,6 +167,36 @@ var azureCustomRoleTestCases = []AzureCustomRoleTestCase{ UpdateExpected: true, ShouldCreateAssignment: false, }, + { + // This test case is for backwards compatibility with the old built-in roles + // The storage blob is a builtin (existing role) that is being used in the new custom role + // No custom role should be created - only assignment + Name: "BackwardsCompatibility", + Roles: []string{ + "Storage Blob Data Reader", + }, + Actions: []otterizev2alpha1.AzureAction{ + "Microsoft.Storage/storageAccounts/blobServices/containers/read", + }, + ExisingRoles: []*armauthorization.RoleDefinition{ + { + ID: to.Ptr("Storage Blob Data Reader"), + Name: to.Ptr("Storage Blob Data Reader"), + Properties: &armauthorization.RoleDefinitionProperties{ + RoleName: to.Ptr("Storage Blob Data Reader"), + Permissions: []*armauthorization.Permission{ + { + Actions: []*string{ + to.Ptr("Microsoft.Storage/storageAccounts/blobServices/containers/read"), + }, + }, + }, + }, + }, + }, + UpdateExpected: false, + ShouldCreateAssignment: true, + }, } func (s *AzureAgentPoliciesCustomRolesSuite) TestAddRolePolicyFromIntents_CustomRoles() { @@ -172,14 +206,13 @@ func (s *AzureAgentPoliciesCustomRolesSuite) TestAddRolePolicyFromIntents_Custom { Azure: &otterizev2alpha1.AzureTarget{ Scope: "/providers/Microsoft.Storage/storageAccounts/test/blobServices/default/containers/container", + Roles: testCase.Roles, Actions: testCase.Actions, DataActions: testCase.DataActions, }, }, } - customRoleScope := fmt.Sprintf("/subscriptions/%s/resourceGroups/%s", testSubscriptionID, testResourceGroup) - clientId := uuid.NewString() s.expectGetUserAssignedIdentityReturnsClientID(clientId) @@ -190,14 +223,16 @@ func (s *AzureAgentPoliciesCustomRolesSuite) TestAddRolePolicyFromIntents_Custom s.expectListRoleAssignmentsReturnsEmpty() // CustomRole related calls - s.expectListRoleDefinitionsReturnsEmpty(customRoleScope, testCase.ExisingCustomRoles) + s.expectListRoleDefinitionsReturnsPager(testCase.ExisingRoles) if testCase.ShouldCreateAssignment { s.expectCreateRoleAssignmentReturnsEmpty() } // Make sure the custom role is created var customRoleDefinition armauthorization.RoleDefinition - s.expectCreateOrUpdateRoleDefinitionWriteRoleDefinition(&customRoleDefinition) + if testCase.UpdateExpected { + s.expectCreateOrUpdateRoleDefinitionWriteRoleDefinition(&customRoleDefinition) + } err := s.agent.AddRolePolicyFromIntents(context.Background(), testNamespace, testAccountName, testIntentsServiceName, intents, corev1.Pod{}) s.NoError(err) From 6598dd5c75542677fd742a4c4703e1fec646eeb4 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Wed, 27 Nov 2024 12:59:00 +0200 Subject: [PATCH 12/15] go generate --- .../otterizecloud/graphqlclient/generated.go | 8 ++++ .../graphqlclient/schema.graphql | 48 +++++++++++++++++++ .../telemetries/telemetriesgql/schema.graphql | 48 +++++++++++++++++++ 3 files changed, 104 insertions(+) diff --git a/src/shared/otterizecloud/graphqlclient/generated.go b/src/shared/otterizecloud/graphqlclient/generated.go index decc39955..6e627e5cb 100644 --- a/src/shared/otterizecloud/graphqlclient/generated.go +++ b/src/shared/otterizecloud/graphqlclient/generated.go @@ -284,6 +284,8 @@ type IntentInput struct { DatabaseResources []*DatabaseConfigInput `json:"databaseResources"` AwsActions []*string `json:"awsActions"` AzureRoles []*string `json:"azureRoles"` + AzureActions []*string `json:"azureActions"` + AzureDataActions []*string `json:"azureDataActions"` AzureKeyVaultPolicy *AzureKeyVaultPolicyInput `json:"azureKeyVaultPolicy"` GcpPermissions []*string `json:"gcpPermissions"` Internet *InternetConfigInput `json:"internet"` @@ -329,6 +331,12 @@ func (v *IntentInput) GetAwsActions() []*string { return v.AwsActions } // GetAzureRoles returns IntentInput.AzureRoles, and is useful for accessing the field via an interface. func (v *IntentInput) GetAzureRoles() []*string { return v.AzureRoles } +// GetAzureActions returns IntentInput.AzureActions, and is useful for accessing the field via an interface. +func (v *IntentInput) GetAzureActions() []*string { return v.AzureActions } + +// GetAzureDataActions returns IntentInput.AzureDataActions, and is useful for accessing the field via an interface. +func (v *IntentInput) GetAzureDataActions() []*string { return v.AzureDataActions } + // GetAzureKeyVaultPolicy returns IntentInput.AzureKeyVaultPolicy, and is useful for accessing the field via an interface. func (v *IntentInput) GetAzureKeyVaultPolicy() *AzureKeyVaultPolicyInput { return v.AzureKeyVaultPolicy diff --git a/src/shared/otterizecloud/graphqlclient/schema.graphql b/src/shared/otterizecloud/graphqlclient/schema.graphql index 526e2d931..02ca1d041 100644 --- a/src/shared/otterizecloud/graphqlclient/schema.graphql +++ b/src/shared/otterizecloud/graphqlclient/schema.graphql @@ -252,6 +252,11 @@ type AzureResource { resource: String! } +type BasicEntity { + id: ID! + name: String! +} + """The `Boolean` scalar type represents `true` or `false`.""" scalar Boolean @@ -707,6 +712,21 @@ type FeatureFlags { isCloudSecurityEnabled: Boolean } +type Finding { + service: BasicEntity! + server: BasicEntity! + cluster: BasicEntity! + reason: String! + status: FindingStatus! +} + +"""NEW findings""" +enum FindingStatus { + OPEN + RESOLVED + IGNORED +} + type FindingSummary { standard: RegulationStandard! codeLabel: String! @@ -721,6 +741,21 @@ type FindingSummary { requirements: [FindingSummary!] } +type FindingSummaryV2 { + standard: RegulationStandard! + code: RegulationCode! + codeLabel: String! + severity: Severity! + description: String! + validationDescription: String + status: FindingStatus! + serviceTotalCount: Int! + serviceOpenCount: Int! + clusterTotalCount: Int! + clusterOpenCount: Int! + requirements: [FindingSummaryV2!] +} + """The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).""" scalar Float @@ -926,6 +961,8 @@ input InputFeatureFlags { input InputFindingFilter { """ Findings filter """ severity: InputIDFilterValue +""" Findings filter """ + status: InputIDFilterValue """ Findings filter """ clusterIds: InputIDFilterValue """ Findings filter """ @@ -1089,6 +1126,8 @@ type Intent { databaseResources: [DatabaseConfig!] awsActions: [String!] azureRoles: [String!] + azureActions: [String!] + azureDataActions: [String!] azureKeyVaultPolicy: AzureKeyVaultPolicy gcpPermissions: [String!] internet: InternetConfig @@ -1109,6 +1148,8 @@ input IntentInput { databaseResources: [DatabaseConfigInput!] awsActions: [String!] azureRoles: [String!] + azureActions: [String!] + azureDataActions: [String!] azureKeyVaultPolicy: AzureKeyVaultPolicyInput gcpPermissions: [String!] internet: InternetConfigInput @@ -1953,6 +1994,13 @@ type Query { filter: InputFindingFilter tree: Boolean ): Dashboard! +"""NEW findings""" + findingSummary( + filter: InputFindingFilter + ): [FindingSummaryV2!]! + findingsV2( + filter: InputFindingFilter + ): [Finding!]! """List integrations""" integrations( name: String diff --git a/src/shared/telemetries/telemetriesgql/schema.graphql b/src/shared/telemetries/telemetriesgql/schema.graphql index 526e2d931..02ca1d041 100644 --- a/src/shared/telemetries/telemetriesgql/schema.graphql +++ b/src/shared/telemetries/telemetriesgql/schema.graphql @@ -252,6 +252,11 @@ type AzureResource { resource: String! } +type BasicEntity { + id: ID! + name: String! +} + """The `Boolean` scalar type represents `true` or `false`.""" scalar Boolean @@ -707,6 +712,21 @@ type FeatureFlags { isCloudSecurityEnabled: Boolean } +type Finding { + service: BasicEntity! + server: BasicEntity! + cluster: BasicEntity! + reason: String! + status: FindingStatus! +} + +"""NEW findings""" +enum FindingStatus { + OPEN + RESOLVED + IGNORED +} + type FindingSummary { standard: RegulationStandard! codeLabel: String! @@ -721,6 +741,21 @@ type FindingSummary { requirements: [FindingSummary!] } +type FindingSummaryV2 { + standard: RegulationStandard! + code: RegulationCode! + codeLabel: String! + severity: Severity! + description: String! + validationDescription: String + status: FindingStatus! + serviceTotalCount: Int! + serviceOpenCount: Int! + clusterTotalCount: Int! + clusterOpenCount: Int! + requirements: [FindingSummaryV2!] +} + """The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point).""" scalar Float @@ -926,6 +961,8 @@ input InputFeatureFlags { input InputFindingFilter { """ Findings filter """ severity: InputIDFilterValue +""" Findings filter """ + status: InputIDFilterValue """ Findings filter """ clusterIds: InputIDFilterValue """ Findings filter """ @@ -1089,6 +1126,8 @@ type Intent { databaseResources: [DatabaseConfig!] awsActions: [String!] azureRoles: [String!] + azureActions: [String!] + azureDataActions: [String!] azureKeyVaultPolicy: AzureKeyVaultPolicy gcpPermissions: [String!] internet: InternetConfig @@ -1109,6 +1148,8 @@ input IntentInput { databaseResources: [DatabaseConfigInput!] awsActions: [String!] azureRoles: [String!] + azureActions: [String!] + azureDataActions: [String!] azureKeyVaultPolicy: AzureKeyVaultPolicyInput gcpPermissions: [String!] internet: InternetConfigInput @@ -1953,6 +1994,13 @@ type Query { filter: InputFindingFilter tree: Boolean ): Dashboard! +"""NEW findings""" + findingSummary( + filter: InputFindingFilter + ): [FindingSummaryV2!]! + findingsV2( + filter: InputFindingFilter + ): [Finding!]! """List integrations""" integrations( name: String From 2601d8ede7f7ebcc22594c2d6c65760476b9d940 Mon Sep 17 00:00:00 2001 From: davidrobert Date: Wed, 27 Nov 2024 13:08:45 +0200 Subject: [PATCH 13/15] Add new azure fields to cloud format --- src/operator/api/v2alpha1/clientintents_types.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/operator/api/v2alpha1/clientintents_types.go b/src/operator/api/v2alpha1/clientintents_types.go index c50a7c163..fdd056995 100644 --- a/src/operator/api/v2alpha1/clientintents_types.go +++ b/src/operator/api/v2alpha1/clientintents_types.go @@ -883,6 +883,13 @@ func (in *Target) ConvertToCloudFormat(ctx context.Context, k8sClient client.Cli if in.Azure != nil { intentInput.AzureRoles = lo.ToSlicePtr(in.Azure.Roles) + intentInput.AzureActions = lo.Map(in.Azure.Actions, func(action AzureAction, _ int) *string { + return lo.ToPtr(string(action)) + }) + intentInput.AzureDataActions = lo.Map(in.Azure.DataActions, func(action AzureDataAction, _ int) *string { + return lo.ToPtr(string(action)) + }) + if in.Azure.KeyVaultPolicy != nil { intentInput.AzureKeyVaultPolicy = &graphqlclient.AzureKeyVaultPolicyInput{ CertificatePermissions: enumSliceToStrPtrSlice(in.Azure.KeyVaultPolicy.CertificatePermissions), From ca0a18d711fb10d3baab0e7c7f4585310cfb2f4e Mon Sep 17 00:00:00 2001 From: davidrobert Date: Wed, 27 Nov 2024 15:59:44 +0200 Subject: [PATCH 14/15] fix review comments --- .../iam/iampolicyagents/azurepolicyagent/agent.go | 10 +++++++++- src/shared/azureagent/customroles.go | 14 ++++---------- src/shared/azureagent/roleassignments.go | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go index 5017573d8..4f0a072db 100644 --- a/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go +++ b/src/operator/controllers/intents_reconcilers/iam/iampolicyagents/azurepolicyagent/agent.go @@ -3,6 +3,7 @@ package azurepolicyagent import ( "context" "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" @@ -373,10 +374,17 @@ func (a *Agent) ensureCustomRoleForIntent(ctx context.Context, userAssignedIdent return errors.Wrap(err) } } else { - err := a.CreateCustomRole(ctx, scope, userAssignedIdentity, actions, dataActions) + newRole, err := a.CreateCustomRole(ctx, scope, userAssignedIdentity, actions, dataActions) if err != nil { return errors.Wrap(err) } + + // create a role assignment for the custom role + err = a.CreateRoleAssignment(ctx, scope, userAssignedIdentity, *newRole, to.Ptr(azureagent.OtterizeCustomRoleTag)) + if err != nil { + // TODO: handle case when custom role is created and role assignment fails + return errors.Wrap(err) + } } return nil diff --git a/src/shared/azureagent/customroles.go b/src/shared/azureagent/customroles.go index a7a32ef53..14b45bd41 100644 --- a/src/shared/azureagent/customroles.go +++ b/src/shared/azureagent/customroles.go @@ -17,7 +17,7 @@ const ( // maxRoleNameLength rules: 3-512 characters maxRoleNameLength = 200 - otterizeCustomRoleTag = "OtterizeCustomRole" + OtterizeCustomRoleTag = "OtterizeCustomRole" ) func (a *Agent) getCustomRoleScope() string { @@ -29,7 +29,7 @@ func (a *Agent) GenerateCustomRoleName(uai armmsi.Identity, scope string) string return agentutils.TruncateHashName(fullName, maxRoleNameLength) } -func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.Identity, actions []v2alpha1.AzureAction, dataActions []v2alpha1.AzureDataAction) error { +func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.Identity, actions []v2alpha1.AzureAction, dataActions []v2alpha1.AzureDataAction) (*armauthorization.RoleDefinition, error) { roleScope := a.getCustomRoleScope() formattedActions := lo.Map(actions, func(action v2alpha1.AzureAction, _ int) *string { @@ -60,16 +60,10 @@ func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.I // create the custom role resp, err := a.roleDefinitionsClient.CreateOrUpdate(ctx, roleScope, id, roleDefinition, nil) if err != nil { - return errors.Wrap(err) - } - - // create a role assignment for the custom role - err = a.CreateRoleAssignment(ctx, scope, uai, resp.RoleDefinition, to.Ptr(otterizeCustomRoleTag)) - if err != nil { - return errors.Wrap(err) + return nil, errors.Wrap(err) } - return nil + return &resp.RoleDefinition, nil } func (a *Agent) UpdateCustomRole(ctx context.Context, role *armauthorization.RoleDefinition, actions []v2alpha1.AzureAction, dataActions []v2alpha1.AzureDataAction) error { diff --git a/src/shared/azureagent/roleassignments.go b/src/shared/azureagent/roleassignments.go index 9d72dfeaa..6dbd47b6c 100644 --- a/src/shared/azureagent/roleassignments.go +++ b/src/shared/azureagent/roleassignments.go @@ -16,7 +16,7 @@ func (a *Agent) IsCustomRoleAssignment(roleAssignment armauthorization.RoleAssig if roleAssignment.Properties.Description == nil { return false } - return *roleAssignment.Properties.Description == otterizeCustomRoleTag + return *roleAssignment.Properties.Description == OtterizeCustomRoleTag } func (a *Agent) CreateRoleAssignment(ctx context.Context, scope string, userAssignedIdentity armmsi.Identity, roleDefinition armauthorization.RoleDefinition, desc *string) error { From 7c14060c8d1efb50dc073873d87529518d55c0fb Mon Sep 17 00:00:00 2001 From: davidrobert Date: Thu, 28 Nov 2024 16:52:04 +0200 Subject: [PATCH 15/15] fix description --- src/shared/azureagent/customroles.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/shared/azureagent/customroles.go b/src/shared/azureagent/customroles.go index 14b45bd41..255792ccb 100644 --- a/src/shared/azureagent/customroles.go +++ b/src/shared/azureagent/customroles.go @@ -17,7 +17,8 @@ const ( // maxRoleNameLength rules: 3-512 characters maxRoleNameLength = 200 - OtterizeCustomRoleTag = "OtterizeCustomRole" + OtterizeCustomRoleTag = "OtterizeCustomRole" + OtterizeCustomRoleDescription = "This custom role was created by the Otterize intents-operator's Azure integration. For more details, go to https://otterize.com" ) func (a *Agent) getCustomRoleScope() string { @@ -41,12 +42,11 @@ func (a *Agent) CreateCustomRole(ctx context.Context, scope string, uai armmsi.I id := uuid.NewString() name := a.GenerateCustomRoleName(uai, scope) - description := fmt.Sprintf("Otterize managed custom role for uai [%s] with permissions for scope [%s]", *uai.Name, scope) roleDefinition := armauthorization.RoleDefinition{ Properties: &armauthorization.RoleDefinitionProperties{ RoleName: to.Ptr(name), - Description: to.Ptr(description), + Description: to.Ptr(OtterizeCustomRoleDescription), AssignableScopes: []*string{to.Ptr(scope)}, // Where the role can be assigned Permissions: []*armauthorization.Permission{ {