Skip to content

Commit

Permalink
New Resource: aws_s3_account_public_access_block
Browse files Browse the repository at this point in the history
This implements Public Access Block configuration within the new S3 Control API, which is separate from the existing S3 API. The S3 Control API features the same eventual consistency complexity as the existing S3 API across all operations. The resource and acceptance testing try handling the eventual consistency as best as possible up to a minute.

The resource is named `aws_s3_account_public_access_block` instead of `aws_s3control_public_access_block` to be more operator friendly with respect to it working at the account level.

Changes:

* provider: Implement S3 Control custom endpoint, session, and connection
* New Resource: aws_s3_account_public_access_block

Output from acceptance testing:

```
--- PASS: TestAccAWSS3Account (169.55s)
    --- PASS: TestAccAWSS3Account/PublicAccessBlock (169.55s)
        --- PASS: TestAccAWSS3Account/PublicAccessBlock/basic (13.01s)
        --- PASS: TestAccAWSS3Account/PublicAccessBlock/disappears (7.95s)
        --- PASS: TestAccAWSS3Account/PublicAccessBlock/AccountId (11.47s)
        --- PASS: TestAccAWSS3Account/PublicAccessBlock/BlockPublicAcls (25.04s)
        --- PASS: TestAccAWSS3Account/PublicAccessBlock/BlockPublicPolicy (25.33s)
        --- PASS: TestAccAWSS3Account/PublicAccessBlock/IgnorePublicAcls (25.33s)
        --- PASS: TestAccAWSS3Account/PublicAccessBlock/RestrictPublicBuckets (26.40s)
```
  • Loading branch information
bflad committed Dec 14, 2018
1 parent fda837b commit 93c112a
Show file tree
Hide file tree
Showing 7 changed files with 699 additions and 0 deletions.
5 changes: 5 additions & 0 deletions aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import (
"github.com/aws/aws-sdk-go/service/redshift"
"github.com/aws/aws-sdk-go/service/route53"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3control"
"github.com/aws/aws-sdk-go/service/secretsmanager"
"github.com/aws/aws-sdk-go/service/securityhub"
"github.com/aws/aws-sdk-go/service/servicecatalog"
Expand Down Expand Up @@ -150,6 +151,7 @@ type Config struct {
RdsEndpoint string
R53Endpoint string
S3Endpoint string
S3ControlEndpoint string
SnsEndpoint string
SqsEndpoint string
StsEndpoint string
Expand Down Expand Up @@ -198,6 +200,7 @@ type AWSClient struct {
appautoscalingconn *applicationautoscaling.ApplicationAutoScaling
autoscalingconn *autoscaling.AutoScaling
s3conn *s3.S3
s3controlconn *s3control.S3Control
secretsmanagerconn *secretsmanager.SecretsManager
securityhubconn *securityhub.SecurityHub
scconn *servicecatalog.ServiceCatalog
Expand Down Expand Up @@ -433,6 +436,7 @@ func (c *Config) Client() (interface{}, error) {
awsKmsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.KmsEndpoint)})
awsRdsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.RdsEndpoint)})
awsS3Sess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3Endpoint)})
awsS3ControlSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.S3ControlEndpoint)})
awsSnsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SnsEndpoint)})
awsSqsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.SqsEndpoint)})
awsStsSess := sess.Copy(&aws.Config{Endpoint: aws.String(c.StsEndpoint)})
Expand Down Expand Up @@ -567,6 +571,7 @@ func (c *Config) Client() (interface{}, error) {
client.redshiftconn = redshift.New(sess)
client.simpledbconn = simpledb.New(sess)
client.s3conn = s3.New(awsS3Sess)
client.s3controlconn = s3control.New(awsS3ControlSess)
client.scconn = servicecatalog.New(sess)
client.sdconn = servicediscovery.New(sess)
client.sesConn = ses.New(sess)
Expand Down
10 changes: 10 additions & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ func Provider() terraform.ResourceProvider {
"aws_ses_event_destination": resourceAwsSesEventDestination(),
"aws_ses_identity_notification_topic": resourceAwsSesNotificationTopic(),
"aws_ses_template": resourceAwsSesTemplate(),
"aws_s3_account_public_access_block": resourceAwsS3AccountPublicAccessBlock(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_s3_bucket_policy": resourceAwsS3BucketPolicy(),
"aws_s3_bucket_object": resourceAwsS3BucketObject(),
Expand Down Expand Up @@ -812,6 +813,8 @@ func init() {

"s3_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n",

"s3control_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n",

"sns_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n",

"sqs_endpoint": "Use this to override the default endpoint URL constructed from the `region`.\n",
Expand Down Expand Up @@ -923,6 +926,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
config.R53Endpoint = endpoints["r53"].(string)
config.RdsEndpoint = endpoints["rds"].(string)
config.S3Endpoint = endpoints["s3"].(string)
config.S3ControlEndpoint = endpoints["s3control"].(string)
config.SnsEndpoint = endpoints["sns"].(string)
config.SqsEndpoint = endpoints["sqs"].(string)
config.StsEndpoint = endpoints["sts"].(string)
Expand Down Expand Up @@ -1128,6 +1132,12 @@ func endpointsSchema() *schema.Schema {
Default: "",
Description: descriptions["s3_endpoint"],
},
"s3control": {
Type: schema.TypeString,
Optional: true,
Default: "",
Description: descriptions["s3control_endpoint"],
},
"sns": {
Type: schema.TypeString,
Optional: true,
Expand Down
182 changes: 182 additions & 0 deletions aws/resource_aws_s3_account_public_access_block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package aws

import (
"fmt"
"log"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3control"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsS3AccountPublicAccessBlock() *schema.Resource {
return &schema.Resource{
Create: resourceAwsS3AccountPublicAccessBlockCreate,
Read: resourceAwsS3AccountPublicAccessBlockRead,
Update: resourceAwsS3AccountPublicAccessBlockUpdate,
Delete: resourceAwsS3AccountPublicAccessBlockDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"account_id": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validateAwsAccountId,
},
"block_public_acls": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"block_public_policy": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"ignore_public_acls": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"restrict_public_buckets": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}

func resourceAwsS3AccountPublicAccessBlockCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn

accountID := meta.(*AWSClient).accountid
if v, ok := d.GetOk("account_id"); ok {
accountID = v.(string)
}

input := &s3control.PutPublicAccessBlockInput{
AccountId: aws.String(accountID),
PublicAccessBlockConfiguration: &s3control.PublicAccessBlockConfiguration{
BlockPublicAcls: aws.Bool(d.Get("block_public_acls").(bool)),
BlockPublicPolicy: aws.Bool(d.Get("block_public_policy").(bool)),
IgnorePublicAcls: aws.Bool(d.Get("ignore_public_acls").(bool)),
RestrictPublicBuckets: aws.Bool(d.Get("restrict_public_buckets").(bool)),
},
}

log.Printf("[DEBUG] Creating S3 Account Public Access Block: %s", input)
_, err := conn.PutPublicAccessBlock(input)
if err != nil {
return fmt.Errorf("error creating S3 Account Public Access Block: %s", err)
}

d.SetId(accountID)

return resourceAwsS3AccountPublicAccessBlockRead(d, meta)
}

func resourceAwsS3AccountPublicAccessBlockRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn

input := &s3control.GetPublicAccessBlockInput{
AccountId: aws.String(d.Id()),
}

// Retry for eventual consistency on creation
var output *s3control.GetPublicAccessBlockOutput
err := resource.Retry(1*time.Minute, func() *resource.RetryError {
var err error
output, err = conn.GetPublicAccessBlock(input)

if d.IsNewResource() && isAWSErr(err, s3control.ErrCodeNoSuchPublicAccessBlockConfiguration, "") {
return resource.RetryableError(err)
}

if err != nil {
return resource.NonRetryableError(err)
}

return nil
})

if isAWSErr(err, s3control.ErrCodeNoSuchPublicAccessBlockConfiguration, "") {
log.Printf("[WARN] S3 Account Public Access Block (%s) not found, removing from state", d.Id())
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading S3 Account Public Access Block: %s", err)
}

if output == nil || output.PublicAccessBlockConfiguration == nil {
return fmt.Errorf("error reading S3 Account Public Access Block (%s): missing public access block configuration", d.Id())
}

d.Set("account_id", d.Id())
d.Set("block_public_acls", output.PublicAccessBlockConfiguration.BlockPublicAcls)
d.Set("block_public_policy", output.PublicAccessBlockConfiguration.BlockPublicPolicy)
d.Set("ignore_public_acls", output.PublicAccessBlockConfiguration.IgnorePublicAcls)
d.Set("restrict_public_buckets", output.PublicAccessBlockConfiguration.RestrictPublicBuckets)

return nil
}

func resourceAwsS3AccountPublicAccessBlockUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn

input := &s3control.PutPublicAccessBlockInput{
AccountId: aws.String(d.Id()),
PublicAccessBlockConfiguration: &s3control.PublicAccessBlockConfiguration{
BlockPublicAcls: aws.Bool(d.Get("block_public_acls").(bool)),
BlockPublicPolicy: aws.Bool(d.Get("block_public_policy").(bool)),
IgnorePublicAcls: aws.Bool(d.Get("ignore_public_acls").(bool)),
RestrictPublicBuckets: aws.Bool(d.Get("restrict_public_buckets").(bool)),
},
}

log.Printf("[DEBUG] Updating S3 Account Public Access Block: %s", input)
_, err := conn.PutPublicAccessBlock(input)
if err != nil {
return fmt.Errorf("error updating S3 Account Public Access Block (%s): %s", d.Id(), err)
}

// Workaround API eventual consistency issues. This type of logic should not normally be used.
// We cannot reliably determine when the Read after Update might be properly updated.
// Rather than introduce complicated retry logic, we presume that a lack of an update error
// means our update succeeded with our expected values.
d.Set("block_public_acls", input.PublicAccessBlockConfiguration.BlockPublicAcls)
d.Set("block_public_policy", input.PublicAccessBlockConfiguration.BlockPublicPolicy)
d.Set("ignore_public_acls", input.PublicAccessBlockConfiguration.IgnorePublicAcls)
d.Set("restrict_public_buckets", input.PublicAccessBlockConfiguration.RestrictPublicBuckets)

// Skip normal Read after Update due to eventual consistency issues
return nil
}

func resourceAwsS3AccountPublicAccessBlockDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).s3controlconn

input := &s3control.DeletePublicAccessBlockInput{
AccountId: aws.String(d.Id()),
}

_, err := conn.DeletePublicAccessBlock(input)

if isAWSErr(err, s3control.ErrCodeNoSuchPublicAccessBlockConfiguration, "") {
return nil
}

if err != nil {
return fmt.Errorf("error deleting S3 Account Public Access Block (%s): %s", d.Id(), err)
}

return nil
}
Loading

0 comments on commit 93c112a

Please sign in to comment.