From 031c146daf59acf478514fe6d610c6857620316c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 20 Feb 2025 16:45:32 +0100 Subject: [PATCH] azurerm_pim_active_role_assignment: add support for RBAC conditions. (#27947) * azurerm_pim_active_role_assignment: add support for RBAC conditions. * restore original test * Update internal/services/authorization/pim_eligible_role_assignment_resource.go Co-authored-by: Wyatt Fry * Update website/docs/r/pim_eligible_role_assignment.html.markdown Co-authored-by: jackofallops <11830746+jackofallops@users.noreply.github.com> * Update internal/services/authorization/pim_eligible_role_assignment_resource_test.go Co-authored-by: Wyatt Fry --------- Co-authored-by: Wyatt Fry Co-authored-by: jackofallops <11830746+jackofallops@users.noreply.github.com> --- .../pim_eligible_role_assignment_resource.go | 51 +++++++++++++--- ..._eligible_role_assignment_resource_test.go | 59 +++++++++++++++++++ ...pim_eligible_role_assignment.html.markdown | 7 +++ 3 files changed, 108 insertions(+), 9 deletions(-) diff --git a/internal/services/authorization/pim_eligible_role_assignment_resource.go b/internal/services/authorization/pim_eligible_role_assignment_resource.go index 4cd7f71c0ffa..14465a13217b 100644 --- a/internal/services/authorization/pim_eligible_role_assignment_resource.go +++ b/internal/services/authorization/pim_eligible_role_assignment_resource.go @@ -40,6 +40,8 @@ type PimEligibleRoleAssignmentModel struct { Justification string `tfschema:"justification"` TicketInfo []PimEligibleRoleAssignmentTicketInfo `tfschema:"ticket"` ScheduleInfo []PimEligibleRoleAssignmentScheduleInfo `tfschema:"schedule"` + Condition string `tfschema:"condition"` + ConditionVersion string `tfschema:"condition_version"` } type PimEligibleRoleAssignmentTicketInfo struct { @@ -114,6 +116,24 @@ func (PimEligibleRoleAssignmentResource) Arguments() map[string]*pluginsdk.Schem Description: "The justification for this eligible role assignment", }, + "condition": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"condition_version"}, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "condition_version": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + RequiredWith: []string{"condition"}, + ValidateFunc: validation.StringInSlice([]string{ + "2.0", + }, false), + }, + "schedule": { Type: pluginsdk.TypeList, MaxItems: 1, @@ -285,16 +305,26 @@ func (r PimEligibleRoleAssignmentResource) Create() sdk.ResourceFunc { return err } + properties := &roleeligibilityschedulerequests.RoleEligibilityScheduleRequestProperties{ + Justification: pointer.To(config.Justification), + PrincipalId: id.PrincipalId, + RequestType: roleeligibilityschedulerequests.RequestTypeAdminAssign, + RoleDefinitionId: id.RoleDefinitionId, + Scope: pointer.To(scopeId.ID()), + ScheduleInfo: scheduleInfo, + TicketInfo: ticketInfo, + } + + condition := config.Condition + conditionVersion := config.ConditionVersion + + if condition != "" && conditionVersion != "" { + properties.Condition = pointer.To(condition) + properties.ConditionVersion = pointer.To(conditionVersion) + } + payload := roleeligibilityschedulerequests.RoleEligibilityScheduleRequest{ - Properties: &roleeligibilityschedulerequests.RoleEligibilityScheduleRequestProperties{ - Justification: pointer.To(config.Justification), - PrincipalId: id.PrincipalId, - RequestType: roleeligibilityschedulerequests.RequestTypeAdminAssign, - RoleDefinitionId: id.RoleDefinitionId, - Scope: pointer.To(scopeId.ID()), - ScheduleInfo: scheduleInfo, - TicketInfo: ticketInfo, - }, + Properties: properties, } roleEligibilityScheduleRequestName, err := uuid.GenerateUUID() @@ -396,6 +426,9 @@ func (r PimEligibleRoleAssignmentResource) Read() sdk.ResourceFunc { state.PrincipalType = string(pointer.From(request.Properties.PrincipalType)) state.RoleDefinitionId = request.Properties.RoleDefinitionId + state.Condition = pointer.From(request.Properties.Condition) + state.ConditionVersion = pointer.From(request.Properties.ConditionVersion) + if ticketInfo := request.Properties.TicketInfo; ticketInfo != nil { if len(state.TicketInfo) == 0 { state.TicketInfo = make([]PimEligibleRoleAssignmentTicketInfo, 1) diff --git a/internal/services/authorization/pim_eligible_role_assignment_resource_test.go b/internal/services/authorization/pim_eligible_role_assignment_resource_test.go index 4913a505d77a..4e95bcf711b1 100644 --- a/internal/services/authorization/pim_eligible_role_assignment_resource_test.go +++ b/internal/services/authorization/pim_eligible_role_assignment_resource_test.go @@ -121,6 +121,21 @@ func TestAccPimEligibleRoleAssignment_expirationByDate(t *testing.T) { }) } +func TestAccPimEligibleRoleAssignment_condition(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_pim_eligible_role_assignment", "test") + r := PimEligibleRoleAssignmentResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.condition(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep("schedule.0.start_date_time"), + }) +} + func (r PimEligibleRoleAssignmentResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := parse.PimRoleAssignmentID(state.ID) if err != nil { @@ -416,3 +431,47 @@ resource "azurerm_pim_eligible_role_assignment" "import" { } `, r.importSetup(data)) } + +func (r PimEligibleRoleAssignmentResource) condition(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_subscription" "primary" {} + +data "azurerm_client_config" "test" {} + +data "azurerm_role_definition" "test" { + name = "Storage Blob Data Contributor" +} + +%[1]s + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[2]d" + location = "%[3]s" +} + +resource "time_static" "test" {} + +resource "azurerm_pim_eligible_role_assignment" "test" { + scope = data.azurerm_subscription.primary.id + role_definition_id = "${data.azurerm_subscription.primary.id}${data.azurerm_role_definition.test.id}" + principal_id = azuread_user.test.object_id + + schedule { + start_date_time = time_static.test.rfc3339 + expiration { + duration_hours = 8 + } + } + + justification = "Expiration Duration Set" + + condition_version = "2.0" + condition = "((!(ActionMatches{'Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read'})) OR (@Resource[Microsoft.Storage/storageAccounts/blobServices/containers:name] StringEquals 'test'))" + + ticket { + number = "1" + system = "example ticket system" + } +} +`, r.template(data), data.RandomInteger, data.Locations.Primary) +} diff --git a/website/docs/r/pim_eligible_role_assignment.html.markdown b/website/docs/r/pim_eligible_role_assignment.html.markdown index a57e2c1087b3..f1d2c9209103 100644 --- a/website/docs/r/pim_eligible_role_assignment.html.markdown +++ b/website/docs/r/pim_eligible_role_assignment.html.markdown @@ -98,6 +98,13 @@ The following arguments are supported: * `ticket` - (Optional) A `ticket` block as defined below. Changing this forces a new resource to be created. + +* `condition` - (Optional) The condition that limits the resources that the role can be assigned to. See the [official conditions documentation](https://learn.microsoft.com/en-us/azure/role-based-access-control/conditions-overview#what-are-role-assignment-conditions) for details. Changing this forces a new resource to be created. + +* `condition_version` - (Optional) The version of the condition. Supported values include `2.0`. Changing this forces a new resource to be created. + +~> **NOTE:** `condition_version` is required when specifying `condition` and vice versa. + --- An `expiration` block supports the following: