Skip to content

Commit

Permalink
Merge pull request #3728 from Chhed13/support-s3-bucket-acl-policies
Browse files Browse the repository at this point in the history
Implementation of acl grants
  • Loading branch information
gdavison authored Mar 4, 2020
2 parents 3b3f176 + b0c2939 commit fad73a2
Show file tree
Hide file tree
Showing 3 changed files with 455 additions and 28 deletions.
213 changes: 207 additions & 6 deletions aws/resource_aws_s3_bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,53 @@ func resourceAwsS3Bucket() *schema.Resource {
},

"acl": {
Type: schema.TypeString,
Default: "private",
Optional: true,
Type: schema.TypeString,
Default: "private",
Optional: true,
ConflictsWith: []string{"grant"},
},

"grant": {
Type: schema.TypeSet,
Optional: true,
Set: grantHash,
ConflictsWith: []string{"acl"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"id": {
Type: schema.TypeString,
Optional: true,
},
"type": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{
s3.TypeCanonicalUser,
s3.TypeGroup,
}, false),
},
"uri": {
Type: schema.TypeString,
Optional: true,
},

"permissions": {
Type: schema.TypeSet,
Required: true,
Set: schema.HashString,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.StringInSlice([]string{
s3.PermissionFullControl,
s3.PermissionRead,
s3.PermissionReadAcp,
s3.PermissionWrite,
s3.PermissionWriteAcp,
}, false),
},
},
},
},
},

"policy": {
Expand Down Expand Up @@ -614,13 +658,17 @@ func resourceAwsS3BucketCreate(d *schema.ResourceData, meta interface{}) error {
bucket = resource.UniqueId()
}
d.Set("bucket", bucket)
acl := d.Get("acl").(string)

log.Printf("[DEBUG] S3 bucket create: %s, ACL: %s", bucket, acl)
log.Printf("[DEBUG] S3 bucket create: %s", bucket)

req := &s3.CreateBucketInput{
Bucket: aws.String(bucket),
ACL: aws.String(acl),
}

if acl, ok := d.GetOk("acl"); ok {
acl := acl.(string)
req.ACL = aws.String(acl)
log.Printf("[DEBUG] S3 bucket %s has canned ACL %s", bucket, acl)
}

var awsRegion string
Expand Down Expand Up @@ -717,6 +765,12 @@ func resourceAwsS3BucketUpdate(d *schema.ResourceData, meta interface{}) error {
}
}

if d.HasChange("grant") {
if err := resourceAwsS3BucketGrantsUpdate(s3conn, d); err != nil {
return err
}
}

if d.HasChange("logging") {
if err := resourceAwsS3BucketLoggingUpdate(s3conn, d); err != nil {
return err
Expand Down Expand Up @@ -836,6 +890,27 @@ func resourceAwsS3BucketRead(d *schema.ResourceData, meta interface{}) error {
}
}

//Read the Grant ACL. Reset if `acl` (canned ACL) is set.
if acl, ok := d.GetOk("acl"); ok && acl.(string) != "private" {
if err := d.Set("grant", nil); err != nil {
return fmt.Errorf("error resetting grant %s", err)
}
} else {
apResponse, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) {
return s3conn.GetBucketAcl(&s3.GetBucketAclInput{
Bucket: aws.String(d.Id()),
})
})
if err != nil {
return fmt.Errorf("error getting S3 Bucket (%s) ACL: %s", d.Id(), err)
}
log.Printf("[DEBUG] S3 bucket: %s, read ACL grants policy: %+v", d.Id(), apResponse)
grants := flattenGrants(apResponse.(*s3.GetBucketAclOutput))
if err := d.Set("grant", schema.NewSet(grantHash, grants)); err != nil {
return fmt.Errorf("error setting grant %s", err)
}
}

// Read the CORS
corsResponse, err := retryOnAwsCode(s3.ErrCodeNoSuchBucket, func() (interface{}, error) {
return s3conn.GetBucketCors(&s3.GetBucketCorsInput{
Expand Down Expand Up @@ -1365,6 +1440,74 @@ func resourceAwsS3BucketPolicyUpdate(s3conn *s3.S3, d *schema.ResourceData) erro
return nil
}

func resourceAwsS3BucketGrantsUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
bucket := d.Get("bucket").(string)
rawGrants := d.Get("grant").(*schema.Set).List()

if len(rawGrants) == 0 {
log.Printf("[DEBUG] S3 bucket: %s, Grants fallback to canned ACL", bucket)
if err := resourceAwsS3BucketAclUpdate(s3conn, d); err != nil {
return fmt.Errorf("Error fallback to canned ACL, %s", err)
}
} else {
apResponse, err := retryOnAwsCode("NoSuchBucket", func() (interface{}, error) {
return s3conn.GetBucketAcl(&s3.GetBucketAclInput{
Bucket: aws.String(d.Id()),
})
})

if err != nil {
return fmt.Errorf("error getting S3 Bucket (%s) ACL: %s", d.Id(), err)
}

ap := apResponse.(*s3.GetBucketAclOutput)
log.Printf("[DEBUG] S3 bucket: %s, read ACL grants policy: %+v", d.Id(), ap)

grants := make([]*s3.Grant, 0, len(rawGrants))
for _, rawGrant := range rawGrants {
log.Printf("[DEBUG] S3 bucket: %s, put grant: %#v", bucket, rawGrant)
grantMap := rawGrant.(map[string]interface{})
for _, rawPermission := range grantMap["permissions"].(*schema.Set).List() {
ge := &s3.Grantee{}
if i, ok := grantMap["id"].(string); ok && i != "" {
ge.SetID(i)
}
if t, ok := grantMap["type"].(string); ok && t != "" {
ge.SetType(t)
}
if u, ok := grantMap["uri"].(string); ok && u != "" {
ge.SetURI(u)
}

g := &s3.Grant{
Grantee: ge,
Permission: aws.String(rawPermission.(string)),
}
grants = append(grants, g)
}
}

grantsInput := &s3.PutBucketAclInput{
Bucket: aws.String(bucket),
AccessControlPolicy: &s3.AccessControlPolicy{
Grants: grants,
Owner: ap.Owner,
},
}

log.Printf("[DEBUG] S3 bucket: %s, put Grants: %#v", bucket, grantsInput)

_, err = retryOnAwsCode("NoSuchBucket", func() (interface{}, error) {
return s3conn.PutBucketAcl(grantsInput)
})

if err != nil {
return fmt.Errorf("Error putting S3 Grants: %s", err)
}
}
return nil
}

func resourceAwsS3BucketCorsUpdate(s3conn *s3.S3, d *schema.ResourceData) error {
bucket := d.Get("bucket").(string)
rawCors := d.Get("cors_rule").([]interface{})
Expand Down Expand Up @@ -2339,6 +2482,24 @@ func validateS3BucketName(value string, region string) error {
return nil
}

func grantHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
if v, ok := m["id"]; ok {
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
}
if v, ok := m["type"]; ok {
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
}
if v, ok := m["uri"]; ok {
buf.WriteString(fmt.Sprintf("%s-", v.(string)))
}
if p, ok := m["permissions"]; ok {
buf.WriteString(fmt.Sprintf("%v-", p.(*schema.Set).List()))
}
return hashcode.String(buf.String())
}

func expirationHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
Expand Down Expand Up @@ -2561,3 +2722,43 @@ func flattenS3ObjectLockConfiguration(conf *s3.ObjectLockConfiguration) []interf

return []interface{}{mConf}
}

func flattenGrants(ap *s3.GetBucketAclOutput) []interface{} {
//if ACL grants contains bucket owner FULL_CONTROL only - it is default "private" acl
if len(ap.Grants) == 1 && aws.StringValue(ap.Grants[0].Grantee.ID) == aws.StringValue(ap.Owner.ID) &&
aws.StringValue(ap.Grants[0].Permission) == s3.PermissionFullControl {
return nil
}

getGrant := func(grants []interface{}, grantee map[string]interface{}) (interface{}, bool) {
for _, pg := range grants {
pgt := pg.(map[string]interface{})
if pgt["type"] == grantee["type"] && pgt["id"] == grantee["id"] && pgt["uri"] == grantee["uri"] &&
pgt["permissions"].(*schema.Set).Len() > 0 {
return pg, true
}
}
return nil, false
}

grants := make([]interface{}, 0, len(ap.Grants))
for _, granteeObject := range ap.Grants {
grantee := make(map[string]interface{})
grantee["type"] = aws.StringValue(granteeObject.Grantee.Type)

if granteeObject.Grantee.ID != nil {
grantee["id"] = aws.StringValue(granteeObject.Grantee.ID)
}
if granteeObject.Grantee.URI != nil {
grantee["uri"] = aws.StringValue(granteeObject.Grantee.URI)
}
if pg, ok := getGrant(grants, grantee); ok {
pg.(map[string]interface{})["permissions"].(*schema.Set).Add(aws.StringValue(granteeObject.Permission))
} else {
grantee["permissions"] = schema.NewSet(schema.HashString, []interface{}{aws.StringValue(granteeObject.Permission)})
grants = append(grants, grantee)
}
}

return grants
}
Loading

0 comments on commit fad73a2

Please sign in to comment.