Skip to content

Commit 53b024e

Browse files
Allow specifying Role Assignment by using the well-known name of a RoleDefinition (#4923)
* Add WellknownResourceReferenceType * Update generated files * Update azure-arm.yaml * Regenerate code * Update tests * Resolve well known role Definitions * Add new test using Contributor as role definition * Add references * Update generated files * Update after rebase * Update configuration * Update generated files * Simplify how we obtain the subscriptionID * Restore correct name of file * Prevent concurrent updates of the map * Prevent reads during updates of map * go mod tidy asoctl * Use RWLock * Fix lock * Added comment about cache expiry
1 parent cf39936 commit 53b024e

30 files changed

+1201
-350
lines changed

v2/api/authorization/customizations/role_assignment_extensions.go

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,22 @@ package customizations
77

88
import (
99
"context"
10+
"fmt"
1011
"strings"
12+
"sync"
13+
14+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
15+
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2"
16+
"github.com/go-logr/logr"
17+
"github.com/rotisserie/eris"
18+
"sigs.k8s.io/controller-runtime/pkg/conversion"
1119

1220
api "github.com/Azure/azure-service-operator/v2/api/authorization/v1api20220401"
21+
storage "github.com/Azure/azure-service-operator/v2/api/authorization/v1api20220401/storage"
22+
"github.com/Azure/azure-service-operator/v2/internal/genericarmclient"
23+
"github.com/Azure/azure-service-operator/v2/internal/reflecthelpers"
24+
"github.com/Azure/azure-service-operator/v2/internal/resolver"
25+
"github.com/Azure/azure-service-operator/v2/internal/util/kubeclient"
1326
"github.com/Azure/azure-service-operator/v2/pkg/genruntime"
1427
"github.com/Azure/azure-service-operator/v2/pkg/genruntime/extensions"
1528
)
@@ -43,3 +56,129 @@ func (extension *RoleAssignmentExtension) Import(
4356

4457
return result, nil
4558
}
59+
60+
var _ extensions.ARMResourceModifier = &RoleAssignmentExtension{}
61+
62+
func (extension *RoleAssignmentExtension) ModifyARMResource(
63+
ctx context.Context,
64+
armClient *genericarmclient.GenericClient,
65+
armObj genruntime.ARMResource,
66+
obj genruntime.ARMMetaObject,
67+
kubeClient kubeclient.Client,
68+
resolver *resolver.Resolver,
69+
log logr.Logger,
70+
) (genruntime.ARMResource, error) {
71+
ra, ok := obj.(*storage.RoleAssignment)
72+
if !ok {
73+
return nil, eris.Errorf(
74+
"Cannot run RoleAssignmentExtension.ModifyARMResource() with unexpected resource type %T",
75+
obj)
76+
}
77+
78+
// Type assert that we are the hub type. This will fail to compile if
79+
// the hub type has been changed but this extension has not been updated to match
80+
var _ conversion.Hub = ra
81+
82+
// If the specified role definition uses a well known name, look it up
83+
roleDefinitionName := ra.Spec.RoleDefinitionReference.WellKnownName
84+
if roleDefinitionName != "" {
85+
err := ensureBuiltInRoleDefinitionsLoaded(ctx, armClient)
86+
if err != nil {
87+
return nil, eris.Wrapf(err, "loading built in role definitions to resolve %q", roleDefinitionName)
88+
}
89+
90+
roleDefinitionId, err := resolveBuiltInRoleDefinition(roleDefinitionName, armObj)
91+
if err != nil {
92+
return nil, eris.Wrapf(err, "resolving built in role definition %q", roleDefinitionName)
93+
}
94+
95+
if roleDefinitionId != roleDefinitionName {
96+
log.V(1).Info("Resolved built-in role", "roleName", roleDefinitionName, "roleId", roleDefinitionId)
97+
98+
spec := armObj.Spec()
99+
err = reflecthelpers.SetProperty(spec, "Properties.RoleDefinitionId", &roleDefinitionId)
100+
if err != nil {
101+
return nil, eris.Wrapf(err, "error setting RoleDefinitionId to %s", roleDefinitionId)
102+
}
103+
104+
return armObj, nil
105+
}
106+
}
107+
108+
return armObj, nil
109+
}
110+
111+
var (
112+
// builtInRoleDefinitions is a cache of all known built-in role definitions, keyed by
113+
// lower case role name. At the time of writing there are ~700 such roles, so we
114+
// pre-initialize the map to sufficient capacity to accommodate those and some modest growth.
115+
builtInRoleDefinitions map[string]string = make(map[string]string, 800)
116+
117+
// builtInRoleDefinitionsLock protects access to builtInRoleDefinitions
118+
builtInRoleDefinitionsLock sync.RWMutex
119+
)
120+
121+
// ensureBuiltInRoleDefinitionsLoaded loads the built-in role definitions into memory if not already loaded.
122+
// We load them once, and then keep them for the lifetime of the pod.
123+
// Given the list of built-in role definitions changes very slowly, this seems reasonable.
124+
func ensureBuiltInRoleDefinitionsLoaded(
125+
ctx context.Context,
126+
armClient *genericarmclient.GenericClient,
127+
) error {
128+
builtInRoleDefinitionsLock.Lock()
129+
defer builtInRoleDefinitionsLock.Unlock()
130+
131+
// Short circuit if already loaded
132+
if len(builtInRoleDefinitions) > 0 {
133+
return nil
134+
}
135+
136+
cl, err := armauthorization.NewRoleDefinitionsClient(armClient.Creds(), armClient.ClientOptions())
137+
if err != nil {
138+
return eris.Wrap(err, "creating client to load built-in role definitions")
139+
}
140+
141+
pager := cl.NewListPager("/", nil)
142+
for pager.More() {
143+
page, err := pager.NextPage(ctx)
144+
if err != nil {
145+
clear(builtInRoleDefinitions) // ensure we'll try again next time
146+
return eris.Wrap(err, "loading built-in role definitions")
147+
}
148+
149+
for _, role := range page.Value {
150+
if *role.Properties.RoleType == "BuiltInRole" {
151+
builtInRoleDefinitions[strings.ToLower(*role.Properties.RoleName)] = *role.Name
152+
}
153+
}
154+
}
155+
156+
return nil
157+
}
158+
159+
func resolveBuiltInRoleDefinition(
160+
roleDefinitionName string,
161+
armObj genruntime.ARMResource,
162+
) (string, error) {
163+
builtInRoleDefinitionsLock.RLock()
164+
defer builtInRoleDefinitionsLock.RUnlock()
165+
166+
roleId, ok := builtInRoleDefinitions[strings.ToLower(roleDefinitionName)]
167+
if !ok {
168+
// If we can't resolve, it, leave it intact
169+
return roleDefinitionName, nil
170+
}
171+
172+
// We need the subscription ID from the resource to construct the ARM ID for a well known role
173+
roleARMId, err := arm.ParseResourceID(armObj.GetID())
174+
if err != nil {
175+
return "", eris.Wrapf(err, "failed to parse the ARM ResourceId for %s", armObj.GetID())
176+
}
177+
178+
armID := fmt.Sprintf(
179+
"/subscriptions/%s/providers/Microsoft.Authorization/roleDefinitions/%s",
180+
roleARMId.SubscriptionID,
181+
roleId)
182+
183+
return armID, nil
184+
}

v2/api/authorization/v1api20200801preview/arm/role_assignment_spec_types_gen.go

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v2/api/authorization/v1api20200801preview/role_assignment_types_gen.go

Lines changed: 13 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v2/api/authorization/v1api20200801preview/storage/role_assignment_types_gen.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v2/api/authorization/v1api20200801preview/storage/structure.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ RoleAssignment: Resource
2222
│ ├── PrincipalIdFromConfig: *genruntime.ConfigMapReference
2323
│ ├── PrincipalType: *string
2424
│ ├── PropertyBag: genruntime.PropertyBag
25-
│ └── RoleDefinitionReference: *genruntime.ResourceReference
25+
│ └── RoleDefinitionReference: *genruntime.WellKnownResourceReference
2626
└── Status: Object (17 properties)
2727
├── Condition: *string
2828
├── ConditionVersion: *string

v2/api/authorization/v1api20200801preview/storage/zz_generated.deepcopy.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v2/api/authorization/v1api20200801preview/structure.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ RoleAssignment: Resource
2323
│ │ ├── "Group"
2424
│ │ ├── "ServicePrincipal"
2525
│ │ └── "User"
26-
│ └── RoleDefinitionReference: *genruntime.ResourceReference
26+
│ └── RoleDefinitionReference: *genruntime.WellKnownResourceReference
2727
└── Status: Object (16 properties)
2828
├── Condition: *string
2929
├── ConditionVersion: *string

v2/api/authorization/v1api20200801preview/zz_generated.deepcopy.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v2/api/authorization/v1api20220401/arm/role_assignment_spec_types_gen.go

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v2/api/authorization/v1api20220401/role_assignment_types_gen.go

Lines changed: 14 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)