diff --git a/docs/resources/group_role_management_policy.md b/docs/resources/group_role_management_policy.md new file mode 100644 index 0000000000..fa15dcffe3 --- /dev/null +++ b/docs/resources/group_role_management_policy.md @@ -0,0 +1,173 @@ +--- +subcategory: "Policies" +--- + +# Resource: azuread_group_role_management_policy + +Manage a role policy for an Azure AD group. + +## API Permissions + +The following API permissions are required in order to use this resource. + +When authenticated with a service principal, this resource requires the `RoleManagementPolicy.ReadWrite.AzureADGroup` Microsoft Graph API permissions. + +When authenticated with a user principal, this resource requires `Global Administrator` directory role, or the `Privileged Role Administrator` role in Identity Governance. + +## Example Usage + +```terraform +resource "azuread_group" "example" { + display_name = "group-name" + security_enabled = true +} + +resource "azuread_user" "member" { + user_principal_name = "jdoe@hashicorp.com" + display_name = "J. Doe" + mail_nickname = "jdoe" + password = "SecretP@sswd99!" +} + +resource "azuread_group_role_management_policy" "example" { + object_id = azuread_group.example.id + assignment_type = "member" + + eligible_assignment_rules { + expiration_required = false + } + + active_assignment_rules { + expire_after = "P365D" + } + + notification_rules { + approver_notifications { + eligible_assignments { + notification_level = "Critical" + default_recipients = false + additional_recipients = [ + "someone@example.com", + "someone.else@example.com", + ] + } + } + } +} +``` + +## Argument Reference + +* `object_id` - (Required) The Object ID of the Azure AD group for which the policy applies. +* `assignment_type` - (Required) The type of assignment this policy coveres. Can be either `member` or `owner`. +* `active_assignment_rules` - (Optional) An `active_assignment_rules` block as defined below. +* `activation_rules` - (Optional) An `activation_rules` block as defined below. +* `eligible_assignment_rules` - (Optional) An `eligible_assignment_rules` block as defined below. +* `notification_rules` - (Optional) An `notification_rules` block as defined below. + +--- + +An `active_assignment_rules` block supports the following: + +* `expiration_required` - (Optional) Must an assignment have an expiry date. `false` allows permanent assignment. +* `expire_after` - (Optional) The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. +* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to create new assignments. +* `require_justification` - (Optional) Is a justification required to create new assignments. +* `require_ticket_info` - (Optional) Is ticket information required to create new assignments. + +One of `expiration_required` or `expire_after` must be provided. + +--- + +An `activation_rules` block supports the following: + +* `maximum_duration` - (Optional) The maximum length of time an activated role can be valid, in an IS)8601 Duration format (e.g. `PT8H`). Valid range is `PT30M` to `PT23H30M`, in 30 minute increments, or `PT1D`. +* `approval_stages` - (Optional) An `approval_stages` block as defined below. +* `require_approval` - (Optional) Is approval required for activation. If `true` an `approval_stages` block must be provided. +* `required_conditional_access_authentication_context` - (Optional) The Entra ID Conditional Access context that must be present for activation. Conflicts with `require_multifactor_authentication`. +* `require_multifactor_authentication` - (Optional) Is multi-factor authentication required to activate the role. Conflicts with `required_conditional_access_authentication_context`. +* `require_justification` - (Optional) Is a justification required during activation of the role. +* `require_ticket_info` - (Optional) Is ticket information requrired during activation of the role. + +--- + +An `admin_notifications` block supports the following: + +* `activations` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of role activations. +* `active_assignments` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of new active assignments. +* `eligible_assignments` An optional `notification_settings` block as defined below for configuring notifications to adminstrators of new eligible assignments. + +--- + +An `approval_stages` block supports the following: + +* One or more `primary_approver` blocks as defined below. + +--- + +An `approver_notifications` block supports the following: + +* `activations` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new activation request. +* `active_assignments` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new active assignment requests. +* `eligible_assignments` - An optional `notification_settings` block as defined below for configuring notifications to approvers of new eligible assignment requests. + +--- + +An `assignee_notifications` block supports the following: + +* `activations` - An optional `notification_settings` block as defined below for configuring notifications to assignees of role activations. +* `active_assignments` - An optional `notification_settings` block as defined below for configuring notifications to assignees of new active assignments. +* `eligible_assignments` - An optional `notification_settings` block as defined below for configuring notifications to assignees of new eligible assignments. + +--- + +An `eligible_assignment_rules` block supports the following: + +* `expiration_required`- Must an assignment have an expiry date. `false` allows permanent assignment. +* `expire_after` - The maximum length of time an assignment can be valid, as an ISO8601 duration. Permitted values: `P15D`, `P30D`, `P90D`, `P180D`, or `P365D`. + +One of `expiration_required` or `expire_after` must be provided. + +--- + +A `notification_rules` block supports the following: + +* `admin_notifications` - (Optional) An `admin_notifications` block as defined above. +* `approver_notifications` - (Optional) An `approver_notifications` block as defined above. +* `assignee_notifications` - (Optional) An `assignee_notifications` block as defined above. + +--- + +A `notification_settings` block supports the following: + +* `notification_level` - (Required) What level of notifications should be sent. Options are `All` or `Critical`. +* `default_recipients` - (Required) Should the default recipients receive these notifications. +* `additional_recipients` - (Optional) A list of additional email addresses that will receive these notifications. + +--- + +A `primary_approver` block supports the following: + +* `user_id` - (Required) The ID of the user or group which will act as an approver. +* `group_id` - (Required) The ID of the user or group which will act as an approver. +* `description` - (Required) A description of the approver. + +Only one of `user_id` or `group_id` can be supplied per block. Multiple approvers can be set by providing multiple `primary_approver` blocks. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` (String) The ID of this policy. +* `display_name` (String) The display name of this policy. +* `description` (String) The description of this policy. + +## Import + +An assignment schedule can be imported using the ID, e.g. + +```shell +terraform import azuread_privileged_access_group_eligibility_schedule_request.example Group_00000000-0000-0000-0000-000000000000_00000000-0000-0000-0000-000000000000 +``` + +Because these policies are created automatically by Entra ID, they will auto-import on first use. diff --git a/internal/services/policies/group_role_management_policy_resource.go b/internal/services/policies/group_role_management_policy_resource.go index 29e65ade84..e723f676fd 100644 --- a/internal/services/policies/group_role_management_policy_resource.go +++ b/internal/services/policies/group_role_management_policy_resource.go @@ -33,6 +33,7 @@ type GroupRoleManagementPolicyActiveAssignmentRules struct { ExpireAfter string `tfschema:"expire_after"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` + RequireTicketInfo bool `tfschema:"require_ticket_info"` } type GroupRoleManagementPolicyEligibleAssignmentRules struct { @@ -44,7 +45,7 @@ type GroupRoleManagementPolicyActivationRules struct { MaximumDuration string `tfschema:"maximum_duration"` RequireApproval bool `tfschema:"require_approval"` ApprovalStages []GroupRoleManagementPolicyApprovalStage `tfschema:"approval_stages"` - RequireConditionalAccessContext string `tfschema:"require_conditional_access_authentication_context"` + RequireConditionalAccessContext string `tfschema:"required_conditional_access_authentication_context"` RequireMultiFactorAuth bool `tfschema:"require_multifactor_authentication"` RequireJustification bool `tfschema:"require_justification"` RequireTicketInfo bool `tfschema:"require_ticket_info"` @@ -56,8 +57,8 @@ type GroupRoleManagementPolicyApprovalStage struct { type GroupRoleManagementPolicyApprover struct { Description string `tfschema:"description"` - ObjectId string `tfschema:"object_id"` - ObjectType string `tfschema:"object_type"` + GroupId string `tfschema:"group_id"` + UserId string `tfschema:"user_id"` } type GroupRoleManagementPolicyNotificationRules struct { @@ -178,6 +179,13 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Optional: true, Computed: true, }, + + "require_ticket_info": { + Description: "Whether ticket information is required to make an assignment", + Type: pluginsdk.TypeBool, + Optional: true, + Computed: true, + }, }, }, }, @@ -231,18 +239,18 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch ValidateDiagFunc: validation.ValidateDiag(validation.StringIsNotEmpty), }, - "object_id": { - Description: "The ID of the useror group to act as an approver", + "group_id": { + Description: "The ID of the group to act as an approver", Type: pluginsdk.TypeString, Required: true, ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, - "object_type": { - Description: "The type of the object to act as an approver", + "user_id": { + Description: "The ID of the user to act as an approver", Type: pluginsdk.TypeString, Required: true, - ValidateDiagFunc: validation.ValidateDiag(validation.StringInSlice([]string{"user", "group"}, false)), + ValidateDiagFunc: validation.ValidateDiag(validation.IsUUID), }, }, }, @@ -251,7 +259,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch }, }, - "require_conditional_access_authentication_context": { + "required_conditional_access_authentication_context": { Description: "Whether a conditional access context is required during activation", Type: pluginsdk.TypeString, Optional: true, @@ -265,7 +273,7 @@ func (r GroupRoleManagementPolicyResource) Arguments() map[string]*pluginsdk.Sch Type: pluginsdk.TypeBool, Optional: true, Computed: true, - ConflictsWith: []string{"activation_rules.0.require_conditional_access_authentication_context"}, + ConflictsWith: []string{"activation_rules.0.required_conditional_access_authentication_context"}, }, "require_justification": { @@ -779,14 +787,12 @@ func (r GroupRoleManagementPolicyResource) Read() sdk.ResourceFunc { case *approver.ODataType == "#microsoft.graph.singleUser": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, - ObjectId: *approver.UserID, - ObjectType: "user", + UserId: *approver.UserID, }) case *approver.ODataType == "#microsoft.graph.groupMembers": primaryApprovers = append(primaryApprovers, GroupRoleManagementPolicyApprover{ Description: *approver.Description, - ObjectId: *approver.GroupID, - ObjectType: "group", + GroupId: *approver.GroupID, }) default: return fmt.Errorf("unknown approver type: %s", *approver.ODataType) @@ -991,6 +997,9 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie if model.ActiveAssignmentRules[0].RequireJustification { enabledRules = append(enabledRules, "Justification") } + if model.ActiveAssignmentRules[0].RequireTicketInfo { + enabledRules = append(enabledRules, "Ticketing") + } rule := msgraph.UnifiedRoleManagementPolicyRule{ ID: policyRules["Enablement_Admin_Assignment"].ID, @@ -1050,20 +1059,23 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie for _, stage := range model.ActivationRules[0].ApprovalStages { primaryApprovers := make([]msgraph.UserSet, 0) for _, approver := range stage.PrimaryApprovers { - if approver.ObjectType == "user" { + if approver.UserId != "" && approver.GroupId != "" { + return nil, fmt.Errorf("Only one of user_id or group_id can be set in a block") + } else if approver.UserId == "" && approver.GroupId == "" { + return nil, fmt.Errorf("One of user_id or group_id must be set in a block") + } + if approver.UserId != "" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ ODataType: pointer.To("#microsoft.graph.singleUser"), - UserID: &approver.ObjectId, + UserID: &approver.UserId, Description: &approver.Description, }) - } else if approver.ObjectType == "group" { + } else if approver.GroupId != "" { primaryApprovers = append(primaryApprovers, msgraph.UserSet{ - ODataType: pointer.To("#microsoft.graph.groupMembers"), - GroupID: &approver.ObjectId, + ODataType: pointer.To("#microsoft.graph.singleUser"), + GroupID: &approver.GroupId, Description: &approver.Description, }) - } else { - return nil, fmt.Errorf("either user_id or group_id must be set") } } @@ -1087,11 +1099,11 @@ func buildPolicyForUpdate(metadata *sdk.ResourceMetaData, policy *msgraph.Unifie updatedRules = append(updatedRules, rule) } - if metadata.ResourceData.HasChange("activation_rules.0.require_conditional_access_authentication_context") { + if metadata.ResourceData.HasChange("activation_rules.0.required_conditional_access_authentication_context") { isEnabled := policyRules["AuthenticationContext_EndUser_Assignment"].IsEnabled claimValue := policyRules["AuthenticationContext_EndUser_Assignment"].ClaimValue - if _, set := metadata.ResourceData.GetOk("activation_rules.0.require_conditional_access_authentication_context"); set { + if _, set := metadata.ResourceData.GetOk("activation_rules.0.required_conditional_access_authentication_context"); set { isEnabled = pointer.To(true) claimValue = pointer.To(model.ActivationRules[0].RequireConditionalAccessContext) } else { diff --git a/internal/services/policies/group_role_management_policy_resource_test.go b/internal/services/policies/group_role_management_policy_resource_test.go index 086f92358f..b0a604c194 100644 --- a/internal/services/policies/group_role_management_policy_resource_test.go +++ b/internal/services/policies/group_role_management_policy_resource_test.go @@ -141,9 +141,8 @@ resource "azuread_group_role_management_policy" "test" { require_approval = true approval_stages { primary_approver { + user_id = azuread_user.approver.object_id description = azuread_user.approver.display_name - object_id = azuread_user.approver.object_id - object_type = "user" } } }