From df3b2d5bf69517ad434d2d2eeef2004307b9242c Mon Sep 17 00:00:00 2001 From: ngharo Date: Fri, 30 Aug 2024 03:40:52 -0500 Subject: [PATCH] Adds LDAP user and group policy attachment resources (#581) --- docs/resources/iam_group_policy_attachment.md | 10 +- .../iam_ldap_group_policy_attachment.md | 56 ++++++ .../iam_ldap_user_policy_attachment.md | 56 ++++++ docs/resources/iam_user_policy_attachment.md | 7 - .../resource.tf | 10 +- .../resource.tf | 28 +++ .../resource.tf | 28 +++ .../resource.tf | 7 - minio/provider.go | 2 + ..._minio_iam_ldap_group_policy_attachment.go | 189 ++++++++++++++++++ ...e_minio_iam_ldap_user_policy_attachment.go | 189 ++++++++++++++++++ 11 files changed, 550 insertions(+), 32 deletions(-) create mode 100644 docs/resources/iam_ldap_group_policy_attachment.md create mode 100644 docs/resources/iam_ldap_user_policy_attachment.md create mode 100644 examples/resources/minio_iam_ldap_group_policy_attachment/resource.tf create mode 100644 examples/resources/minio_iam_ldap_user_policy_attachment/resource.tf create mode 100644 minio/resource_minio_iam_ldap_group_policy_attachment.go create mode 100644 minio/resource_minio_iam_ldap_user_policy_attachment.go diff --git a/docs/resources/iam_group_policy_attachment.md b/docs/resources/iam_group_policy_attachment.md index 83fa47c8..04a14cd6 100644 --- a/docs/resources/iam_group_policy_attachment.md +++ b/docs/resources/iam_group_policy_attachment.md @@ -17,7 +17,7 @@ resource "minio_iam_group" "developer" { name = "developer" } -resource "minio_iam_group_policy" "test_policy" { +resource "minio_iam_policy" "test_policy" { name = "state-terraform-s3" policy = < diff --git a/docs/resources/iam_ldap_group_policy_attachment.md b/docs/resources/iam_ldap_group_policy_attachment.md new file mode 100644 index 00000000..9d53d0a1 --- /dev/null +++ b/docs/resources/iam_ldap_group_policy_attachment.md @@ -0,0 +1,56 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "minio_iam_ldap_group_policy_attachment Resource - terraform-provider-minio" +subcategory: "" +description: |- + Attaches LDAP group to a policy. Can be used against both built-in and user-defined policies. +--- + +# minio_iam_ldap_group_policy_attachment (Resource) + +Attaches LDAP group to a policy. Can be used against both built-in and user-defined policies. + +## Example Usage + +```terraform +resource "minio_iam_policy" "test_policy" { + name = "state-terraform-s3" + policy = < +## Schema + +### Required + +- `group_dn` (String) The distinguished name (dn) of group to attach policy to +- `policy_name` (String) Name of policy to attach to group + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/resources/iam_ldap_user_policy_attachment.md b/docs/resources/iam_ldap_user_policy_attachment.md new file mode 100644 index 00000000..05aef762 --- /dev/null +++ b/docs/resources/iam_ldap_user_policy_attachment.md @@ -0,0 +1,56 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "minio_iam_ldap_user_policy_attachment Resource - terraform-provider-minio" +subcategory: "" +description: |- + Attaches LDAP user to a policy. Can be used against both built-in and user-defined policies. +--- + +# minio_iam_ldap_user_policy_attachment (Resource) + +Attaches LDAP user to a policy. Can be used against both built-in and user-defined policies. + +## Example Usage + +```terraform +resource "minio_iam_policy" "test_policy" { + name = "state-terraform-s3" + policy = < +## Schema + +### Required + +- `policy_name` (String) Name of policy to attach to user +- `user_dn` (String) The dn of user to attach policy to + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/resources/iam_user_policy_attachment.md b/docs/resources/iam_user_policy_attachment.md index 0fff02bd..7ea5ff7f 100644 --- a/docs/resources/iam_user_policy_attachment.md +++ b/docs/resources/iam_user_policy_attachment.md @@ -51,13 +51,6 @@ output "minio_users" { output "minio_group" { value = minio_iam_user_policy_attachment.developer.policy_name } - -# Example using an LDAP User instead of a static MinIO group - -resource "minio_iam_user_policy_attachment" "developer" { - user_name = "CN=My User,OU=Unit,DC=example,DC=com" - policy_name = minio_iam_policy.test_policy.id -} ``` diff --git a/examples/resources/minio_iam_group_policy_attachment/resource.tf b/examples/resources/minio_iam_group_policy_attachment/resource.tf index b89f0cf6..285259fe 100644 --- a/examples/resources/minio_iam_group_policy_attachment/resource.tf +++ b/examples/resources/minio_iam_group_policy_attachment/resource.tf @@ -2,7 +2,7 @@ resource "minio_iam_group" "developer" { name = "developer" } -resource "minio_iam_group_policy" "test_policy" { +resource "minio_iam_policy" "test_policy" { name = "state-terraform-s3" policy = </", d.Id()) + } + + groupDN := idParts[0] + policyName := idParts[1] + + err := d.Set("group_dn", groupDN) + if err != nil { + return nil, errors.New(NewResourceErrorStr("unable to import group policy", groupDN, err)) + } + err = d.Set("policy_name", policyName) + if err != nil { + return nil, errors.New(NewResourceErrorStr("unable to import group policy", groupDN, err)) + } + + d.SetId(fmt.Sprintf("%s/%s", policyName, groupDN)) + return []*schema.ResourceData{d}, nil +} + +func minioReadLDAPGroupPolicies(ctx context.Context, minioAdmin *madmin.AdminClient, groupDN string) ([]string, diag.Diagnostics) { + policyEntities, err := minioAdmin.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{ + Groups: []string{groupDN}, + }) + + if err != nil { + return nil, NewResourceError("failed to load group infos", groupDN, err) + } + + if len(policyEntities.GroupMappings) == 0 { + return nil, nil + } + + if len(policyEntities.GroupMappings) > 1 { + return nil, NewResourceError("failed to load user infos", groupDN, errors.New("more than one group returned when getting LDAP policies for single group")) + } + + return policyEntities.GroupMappings[0].Policies, nil +} diff --git a/minio/resource_minio_iam_ldap_user_policy_attachment.go b/minio/resource_minio_iam_ldap_user_policy_attachment.go new file mode 100644 index 00000000..b544264b --- /dev/null +++ b/minio/resource_minio_iam_ldap_user_policy_attachment.go @@ -0,0 +1,189 @@ +package minio + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/minio/madmin-go/v3" +) + +var ldapUserPolicyAttachmentLock = NewMutexKV() + +func resourceMinioIAMLDAPUserPolicyAttachment() *schema.Resource { + return &schema.Resource{ + Description: "Attaches LDAP user to a policy. Can be used against both built-in and user-defined policies.", + CreateContext: minioCreateLDAPUserPolicyAttachment, + ReadContext: minioReadLDAPUserPolicyAttachment, + DeleteContext: minioDeleteLDAPUserPolicyAttachment, + Importer: &schema.ResourceImporter{ + StateContext: minioImportLDAPUserPolicyAttachment, + }, + Schema: map[string]*schema.Schema{ + "policy_name": { + Type: schema.TypeString, + Description: "Name of policy to attach to user", + Required: true, + ForceNew: true, + ValidateFunc: validateIAMNamePolicy, + }, + "user_dn": { + Type: schema.TypeString, + Description: "The dn of user to attach policy to", + Required: true, + ForceNew: true, + ValidateFunc: validateMinioIamUserName, + }, + }, + } +} + +func minioCreateLDAPUserPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + minioAdmin := meta.(*S3MinioClient).S3Admin + + var userDN = d.Get("user_dn").(string) + var policyName = d.Get("policy_name").(string) + + ldapUserPolicyAttachmentLock.Lock(userDN) + defer ldapUserPolicyAttachmentLock.Unlock(userDN) + + policies, err := minioReadLDAPUserPolicies(ctx, minioAdmin, userDN) + if err != nil { + return err + } + + log.Printf("[DEBUG] '%s' user policies: %v", userDN, policies) + + if !Contains(policies, policyName) { + log.Printf("[DEBUG] Attaching policy %s to user: %s", policyName, userDN) + paResp, err := minioAdmin.AttachPolicyLDAP(ctx, madmin.PolicyAssociationReq{ + Policies: []string{policyName}, + User: userDN, + }) + + log.Printf("[DEBUG] PolicyAssociationResp: %v", paResp) + + if err != nil { + return NewResourceError(fmt.Sprintf("Unable to attach user to policy '%s'", policyName), userDN, err) + } + } + + d.SetId(fmt.Sprintf("%s/%s", policyName, userDN)) + + return doMinioReadLDAPUserPolicyAttachment(ctx, d, meta, userDN, policyName) +} + +func minioReadLDAPUserPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var userDN = d.Get("user_dn").(string) + var policyName = d.Get("policy_name").(string) + + ldapUserPolicyAttachmentLock.Lock(userDN) + defer ldapUserPolicyAttachmentLock.Unlock(userDN) + + return doMinioReadLDAPUserPolicyAttachment(ctx, d, meta, userDN, policyName) +} + +func doMinioReadLDAPUserPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}, userDN, policyName string) diag.Diagnostics { + minioAdmin := meta.(*S3MinioClient).S3Admin + + per, err := minioAdmin.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{ + Policy: []string{policyName}, + Users: []string{userDN}, + }) + + if err != nil { + return NewResourceError(fmt.Sprintf("Failed to query for user policy '%s'", policyName), userDN, err) + } + + log.Printf("[DEBUG] PolicyEntityResponse: %v", per) + if len(per.PolicyMappings) == 0 { + log.Printf("[WARN] No such policy association (%s) found, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err := d.Set("policy_name", policyName); err != nil { + return NewResourceError("failed to load user infos", userDN, err) + } + + return nil +} + +func minioDeleteLDAPUserPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + minioAdmin := meta.(*S3MinioClient).S3Admin + + var userDN = d.Get("user_dn").(string) + var policyName = d.Get("policy_name").(string) + + ldapUserPolicyAttachmentLock.Lock(userDN) + defer ldapUserPolicyAttachmentLock.Unlock(userDN) + + policies, err := minioReadLDAPUserPolicies(ctx, minioAdmin, userDN) + if err != nil { + return err + } + + _, found := Filter(policies, policyName) + if !found { + return nil + } + + paResp, detachErr := minioAdmin.DetachPolicyLDAP(ctx, madmin.PolicyAssociationReq{ + Policies: []string{policyName}, + User: userDN, + }) + + log.Printf("[DEBUG] PolicyAssociationResp: %v", paResp) + + if detachErr != nil { + return NewResourceError(fmt.Sprintf("Unable to detach policy '%s'", policyName), userDN, detachErr) + } + + return nil +} + +func minioImportLDAPUserPolicyAttachment(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.SplitN(d.Id(), "/", 2) + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected /", d.Id()) + } + + userDN := idParts[0] + policyName := idParts[1] + + err := d.Set("user_dn", userDN) + if err != nil { + return nil, errors.New(NewResourceErrorStr("unable to import user policy", userDN, err)) + } + err = d.Set("policy_name", policyName) + if err != nil { + return nil, errors.New(NewResourceErrorStr("unable to import user policy", userDN, err)) + } + + d.SetId(fmt.Sprintf("%s/%s", policyName, userDN)) + return []*schema.ResourceData{d}, nil +} + +func minioReadLDAPUserPolicies(ctx context.Context, minioAdmin *madmin.AdminClient, userDN string) ([]string, diag.Diagnostics) { + policyEntities, err := minioAdmin.GetLDAPPolicyEntities(ctx, madmin.PolicyEntitiesQuery{ + Users: []string{userDN}, + }) + + if err != nil { + return nil, NewResourceError("failed to load user infos", userDN, err) + } + + if len(policyEntities.UserMappings) == 0 { + return nil, nil + } + + if len(policyEntities.UserMappings) > 1 { + return nil, NewResourceError("failed to load user infos", userDN, errors.New("more than one user returned when getting LDAP policies for single user")) + } + + return policyEntities.UserMappings[0].Policies, nil +}