diff --git a/internal/services/authorization/parse/role_assignment.go b/internal/services/authorization/parse/role_assignment.go index 5a0c67139d05..5a3393d9d5dc 100644 --- a/internal/services/authorization/parse/role_assignment.go +++ b/internal/services/authorization/parse/role_assignment.go @@ -10,19 +10,21 @@ import ( // TODO: @tombuildsstuff: this wants refactoring and fixing into sub-ID parsers type RoleAssignmentId struct { - SubscriptionID string - ResourceGroup string - ManagementGroup string - ResourceScope string - ResourceProvider string - Name string - TenantId string - IsSubscriptionLevel bool + SubscriptionID string + ResourceGroup string + ManagementGroup string + ResourceScope string + ResourceProvider string + Name string + SubscriptionAlias string + TenantId string + IsSubscriptionLevel bool + IsSubscriptionAliasLevel bool } -func NewRoleAssignmentID(subscriptionId, resourceGroup, resourceProvider, resourceScope, managementGroup, name, tenantId string, isSubLevel bool) (*RoleAssignmentId, error) { - if subscriptionId == "" && resourceGroup == "" && managementGroup == "" && !isSubLevel { - return nil, fmt.Errorf("one of subscriptionId, resourceGroup, managementGroup or isSubscriptionLevel must be provided") +func NewRoleAssignmentID(subscriptionId, resourceGroup, resourceProvider, resourceScope, managementGroup, name, tenantId, subscriptionAlias string, isSubLevel bool, isSubAliasLevel bool) (*RoleAssignmentId, error) { + if subscriptionId == "" && resourceGroup == "" && managementGroup == "" && !isSubLevel && !isSubAliasLevel { + return nil, fmt.Errorf("one of subscriptionId, resourceGroup, managementGroup, isSubscriptionLevel or isSubscriptionAliasLevel must be provided") } if managementGroup != "" { @@ -37,6 +39,12 @@ func NewRoleAssignmentID(subscriptionId, resourceGroup, resourceProvider, resour } } + if isSubAliasLevel { + if subscriptionId != "" || resourceGroup != "" || managementGroup != "" { + return nil, fmt.Errorf("cannot provide subscriptionId, resourceGroup or managementGroup when isSubscriptionAliasLevel is provided") + } + } + if resourceGroup != "" { if subscriptionId == "" { return nil, fmt.Errorf("subscriptionId must not be empty when resourceGroup is provided") @@ -44,14 +52,16 @@ func NewRoleAssignmentID(subscriptionId, resourceGroup, resourceProvider, resour } return &RoleAssignmentId{ - SubscriptionID: subscriptionId, - ResourceGroup: resourceGroup, - ResourceProvider: resourceProvider, - ResourceScope: resourceScope, - ManagementGroup: managementGroup, - Name: name, - TenantId: tenantId, - IsSubscriptionLevel: isSubLevel, + SubscriptionID: subscriptionId, + ResourceGroup: resourceGroup, + ResourceProvider: resourceProvider, + ResourceScope: resourceScope, + ManagementGroup: managementGroup, + SubscriptionAlias: subscriptionAlias, + Name: name, + TenantId: tenantId, + IsSubscriptionLevel: isSubLevel, + IsSubscriptionAliasLevel: isSubAliasLevel, }, nil } @@ -73,6 +83,11 @@ func (id RoleAssignmentId) AzureResourceID() string { return fmt.Sprintf(fmtString, id.SubscriptionID, id.ResourceGroup, id.Name) } + if id.IsSubscriptionAliasLevel { + fmtString := "/providers/Microsoft.Subscription/aliases/%s/providers/Microsoft.Authorization/roleAssignments/%s" + return fmt.Sprintf(fmtString, id.SubscriptionAlias, id.Name) + } + if id.IsSubscriptionLevel { fmtString := "/providers/Microsoft.Subscription/providers/Microsoft.Authorization/roleAssignments/%s" return fmt.Sprintf(fmtString, id.Name) @@ -131,7 +146,14 @@ func RoleAssignmentID(input string) (*RoleAssignmentId, error) { if len(idParts) != 2 { return nil, fmt.Errorf("could not parse Role Assignment ID %q for subscription scope", input) } - roleAssignmentId.IsSubscriptionLevel = true + if strings.Contains(input, "/aliases/") { + roleAssignmentId.IsSubscriptionAliasLevel = true + aliasParts := strings.Split(idParts[0], "/") + alias := aliasParts[len(aliasParts)-1] + roleAssignmentId.SubscriptionAlias = alias + } else { + roleAssignmentId.IsSubscriptionLevel = true + } if idParts[1] == "" { return nil, fmt.Errorf("ID was missing a value for the roleAssignments element") } diff --git a/internal/services/authorization/parse/role_assignment_test.go b/internal/services/authorization/parse/role_assignment_test.go index 01b2b8d03230..62c3cc77dcb1 100644 --- a/internal/services/authorization/parse/role_assignment_test.go +++ b/internal/services/authorization/parse/role_assignment_test.go @@ -6,15 +6,17 @@ import ( func TestRoleAssignmentIDFormatter(t *testing.T) { testData := []struct { - SubscriptionId string - ResourceGroup string - ResourceProvider string - ResourceScope string - ManagementGroup string - IsSubscriptionLevel bool - Name string - TenantId string - Expected string + SubscriptionId string + ResourceGroup string + ResourceProvider string + ResourceScope string + ManagementGroup string + SubscriptionAlias string + IsSubscriptionLevel bool + IsSubscriptionAliasLevel bool + Name string + TenantId string + Expected string }{ { SubscriptionId: "", @@ -92,10 +94,17 @@ func TestRoleAssignmentIDFormatter(t *testing.T) { TenantId: "34567812-3456-7653-6742-345678901234", Expected: "/providers/Microsoft.Subscription/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121|34567812-3456-7653-6742-345678901234", }, + { + IsSubscriptionAliasLevel: true, + Name: "23456781-2349-8764-5631-234567890121", + TenantId: "34567812-3456-7653-6742-345678901234", + SubscriptionAlias: "my-awesome-sub", + Expected: "/providers/Microsoft.Subscription/aliases/my-awesome-sub/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121|34567812-3456-7653-6742-345678901234", + }, } for _, v := range testData { t.Logf("testing %+v", v) - actual, err := NewRoleAssignmentID(v.SubscriptionId, v.ResourceGroup, v.ResourceProvider, v.ResourceScope, v.ManagementGroup, v.Name, v.TenantId, v.IsSubscriptionLevel) + actual, err := NewRoleAssignmentID(v.SubscriptionId, v.ResourceGroup, v.ResourceProvider, v.ResourceScope, v.ManagementGroup, v.Name, v.TenantId, v.SubscriptionAlias, v.IsSubscriptionLevel, v.IsSubscriptionAliasLevel) if err != nil { if v.Expected == "" { continue @@ -166,6 +175,16 @@ func TestRoleAssignmentID(t *testing.T) { }, }, + { + // valid at subscriptions aliases scope + Input: "/providers/Microsoft.Subscription/aliases/my-awesome-sub/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121", + Expected: &RoleAssignmentId{ + IsSubscriptionAliasLevel: true, + Name: "23456781-2349-8764-5631-234567890121", + SubscriptionAlias: "my-awesome-sub", + }, + }, + { // valid at subscription scope Input: "/subscriptions/12345678-1234-9876-4563-123456789012/providers/Microsoft.Authorization/roleAssignments/23456781-2349-8764-5631-234567890121", @@ -274,5 +293,18 @@ func TestRoleAssignmentID(t *testing.T) { if actual.ManagementGroup != v.Expected.ManagementGroup { t.Fatalf("Expected %q but got %q for Role Assignment Management Group", v.Expected.ManagementGroup, actual.ManagementGroup) } + + if actual.IsSubscriptionLevel != v.Expected.IsSubscriptionLevel { + t.Fatalf("Expected %v but got %v for Role Assignment SubscriptionLevel flag", v.Expected.IsSubscriptionLevel, actual.IsSubscriptionLevel) + } + + if actual.IsSubscriptionAliasLevel != v.Expected.IsSubscriptionAliasLevel { + t.Fatalf("Expected %v but got %v for Role Assignment SubscriptionAliasLevel flag", v.Expected.IsSubscriptionAliasLevel, actual.IsSubscriptionAliasLevel) + } + + if actual.SubscriptionAlias != v.Expected.SubscriptionAlias { + t.Fatalf("Expected %q but got %q for Role Assignment SubscriptionAlias", v.Expected.SubscriptionAlias, actual.SubscriptionAlias) + } + } } diff --git a/internal/services/authorization/role_assignment_resource.go b/internal/services/authorization/role_assignment_resource.go index 8a8bd491112d..099d99819c41 100644 --- a/internal/services/authorization/role_assignment_resource.go +++ b/internal/services/authorization/role_assignment_resource.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "regexp" "strings" "time" @@ -60,9 +61,7 @@ func resourceArmRoleAssignment() *pluginsdk.Resource { // Elevated access for a global admin is needed to assign roles in this scope: // https://docs.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin#azure-cli // It seems only user account is allowed to be elevated access. - validation.StringInSlice([]string{ - "/providers/Microsoft.Subscription", - }, false), + validation.StringMatch(regexp.MustCompile("/providers/Microsoft.Subscription.*"), "Subscription scope is invalid"), billingValidate.EnrollmentID, commonids.ValidateManagementGroupID,