Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resource/aws_s3_access_point: Support S3 on Outposts #15621

Merged
merged 3 commits into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 65 additions & 14 deletions aws/resource_aws_s3_access_point.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,24 @@ func resourceAwsS3AccessPointCreate(d *schema.ResourceData, meta interface{}) er
}

log.Printf("[DEBUG] Creating S3 Access Point: %s", input)
_, err := conn.CreateAccessPoint(input)
output, err := conn.CreateAccessPoint(input)

if err != nil {
return fmt.Errorf("error creating S3 Access Point: %s", err)
return fmt.Errorf("error creating S3 Control Access Point (%s): %w", name, err)
}

if output == nil {
return fmt.Errorf("error creating S3 Control Access Point (%s): empty response", name)
}

d.SetId(fmt.Sprintf("%s:%s", accountId, name))
parsedARN, err := arn.Parse(aws.StringValue(output.AccessPointArn))

if err == nil && strings.HasPrefix(parsedARN.Resource, "outpost/") {
d.SetId(aws.StringValue(output.AccessPointArn))
name = aws.StringValue(output.AccessPointArn)
} else {
d.SetId(fmt.Sprintf("%s:%s", accountId, name))
}

if v, ok := d.GetOk("policy"); ok {
log.Printf("[DEBUG] Putting S3 Access Point policy: %s", d.Id())
Expand Down Expand Up @@ -183,19 +195,44 @@ func resourceAwsS3AccessPointRead(d *schema.ResourceData, meta interface{}) erro
return fmt.Errorf("error reading S3 Access Point (%s): %s", d.Id(), err)
}

name = aws.StringValue(output.Name)
arn := arn.ARN{
AccountID: accountId,
Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Resource: fmt.Sprintf("accesspoint/%s", name),
Service: "s3",
if strings.HasPrefix(name, "arn:") {
parsedAccessPointARN, err := arn.Parse(name)

if err != nil {
return fmt.Errorf("error parsing S3 Control Access Point ARN (%s): %w", name, err)
}

bucketARN := arn.ARN{
AccountID: parsedAccessPointARN.AccountID,
Partition: parsedAccessPointARN.Partition,
Region: parsedAccessPointARN.Region,
Resource: strings.Replace(
parsedAccessPointARN.Resource,
fmt.Sprintf("accesspoint/%s", aws.StringValue(output.Name)),
fmt.Sprintf("bucket/%s", aws.StringValue(output.Bucket)),
1,
),
Service: parsedAccessPointARN.Service,
}

d.Set("arn", name)
d.Set("bucket", bucketARN.String())
} else {
accessPointARN := arn.ARN{
AccountID: accountId,
Partition: meta.(*AWSClient).partition,
Region: meta.(*AWSClient).region,
Resource: fmt.Sprintf("accesspoint/%s", aws.StringValue(output.Name)),
Service: "s3",
}

d.Set("arn", accessPointARN.String())
d.Set("bucket", output.Bucket)
}

d.Set("account_id", accountId)
d.Set("arn", arn.String())
d.Set("bucket", output.Bucket)
d.Set("domain_name", meta.(*AWSClient).RegionalHostname(fmt.Sprintf("%s-%s.s3-accesspoint", name, accountId)))
d.Set("name", name)
d.Set("domain_name", meta.(*AWSClient).RegionalHostname(fmt.Sprintf("%s-%s.s3-accesspoint", aws.StringValue(output.Name), accountId)))
d.Set("name", output.Name)
d.Set("network_origin", output.NetworkOrigin)
if err := d.Set("public_access_block_configuration", flattenS3AccessPointPublicAccessBlockConfiguration(output.PublicAccessBlockConfiguration)); err != nil {
return fmt.Errorf("error setting public_access_block_configuration: %s", err)
Expand All @@ -219,6 +256,13 @@ func resourceAwsS3AccessPointRead(d *schema.ResourceData, meta interface{}) erro
d.Set("policy", policyOutput.Policy)
}

// Return early since S3 on Outposts cannot have public policies
if strings.HasPrefix(name, "arn:") {
d.Set("has_public_access_policy", false)

return nil
}

policyStatusOutput, err := conn.GetAccessPointPolicyStatus(&s3control.GetAccessPointPolicyStatusInput{
AccountId: aws.String(accountId),
Name: aws.String(name),
Expand Down Expand Up @@ -298,7 +342,14 @@ func resourceAwsS3AccessPointDelete(d *schema.ResourceData, meta interface{}) er
return nil
}

// s3AccessPointParseId returns the Account ID and Access Point Name (S3) or ARN (S3 on Outposts)
func s3AccessPointParseId(id string) (string, string, error) {
parsedARN, err := arn.Parse(id)

if err == nil {
return parsedARN.AccountID, id, nil
}

parts := strings.SplitN(id, ":", 2)

if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
Expand Down
72 changes: 72 additions & 0 deletions aws/resource_aws_s3_access_point_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,46 @@ func TestAccAWSS3AccessPoint_bucketDisappears(t *testing.T) {
})
}

func TestAccAWSS3AccessPoint_Bucket_Arn(t *testing.T) {
var v s3control.GetAccessPointOutput
rName := acctest.RandomWithPrefix("tf-acc-test")
resourceName := "aws_s3_access_point.test"

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t); testAccPreCheckAWSOutpostsOutposts(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSS3AccessPointDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSS3AccessPointConfig_Bucket_Arn(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSS3AccessPointExists(resourceName, &v),
testAccCheckResourceAttrAccountID(resourceName, "account_id"),
testAccCheckResourceAttrRegionalARN(resourceName, "arn", "s3-outposts", fmt.Sprintf("outpost/[^/]+/accesspoint/%s", rName)),
resource.TestCheckResourceAttrPair(resourceName, "bucket", "aws_s3control_bucket.test", "arn"),
testAccMatchResourceAttrRegionalHostname(resourceName, "domain_name", "s3-accesspoint", regexp.MustCompile(fmt.Sprintf("^%s-\\d{12}", rName))),
resource.TestCheckResourceAttr(resourceName, "has_public_access_policy", "false"),
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "network_origin", "Vpc"),
resource.TestCheckResourceAttr(resourceName, "policy", ""),
resource.TestCheckResourceAttr(resourceName, "public_access_block_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "public_access_block_configuration.0.block_public_acls", "true"),
resource.TestCheckResourceAttr(resourceName, "public_access_block_configuration.0.block_public_policy", "true"),
resource.TestCheckResourceAttr(resourceName, "public_access_block_configuration.0.ignore_public_acls", "true"),
resource.TestCheckResourceAttr(resourceName, "public_access_block_configuration.0.restrict_public_buckets", "true"),
resource.TestCheckResourceAttr(resourceName, "vpc_configuration.#", "1"),
resource.TestCheckResourceAttrPair(resourceName, "vpc_configuration.0.vpc_id", "aws_vpc.test", "id"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAWSS3AccessPoint_Policy(t *testing.T) {
var v s3control.GetAccessPointOutput
rName := acctest.RandomWithPrefix("tf-acc-test")
Expand Down Expand Up @@ -477,6 +517,38 @@ resource "aws_s3_access_point" "test" {
`, bucketName, accessPointName)
}

func testAccAWSS3AccessPointConfig_Bucket_Arn(rName string) string {
return fmt.Sprintf(`
data "aws_outposts_outposts" "test" {}

data "aws_outposts_outpost" "test" {
id = tolist(data.aws_outposts_outposts.test.ids)[0]
}

resource "aws_s3control_bucket" "test" {
bucket = %[1]q
outpost_id = data.aws_outposts_outpost.test.id
}

resource "aws_vpc" "test" {
cidr_block = "10.0.0.0/16"

tags = {
Name = %[1]q
}
}

resource "aws_s3_access_point" "test" {
bucket = aws_s3control_bucket.test.arn
name = %[1]q

vpc_configuration {
vpc_id = aws_vpc.test.id
}
}
`, rName)
}

func testAccAWSS3AccessPointConfig_policy(rName string) string {
return fmt.Sprintf(`
resource "aws_s3_bucket" "test" {
Expand Down
23 changes: 15 additions & 8 deletions website/docs/r/s3_access_point.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Provides a resource to manage an S3 Access Point.

## Example Usage

### Basic Usage
### AWS Partition Bucket

```hcl
resource "aws_s3_bucket" "example" {
Expand All @@ -27,17 +27,18 @@ resource "aws_s3_access_point" "example" {
}
```

### Access Point Restricted to a VPC
### S3 on Outposts Bucket

```hcl
resource "aws_s3_bucket" "example" {
resource "aws_s3control_bucket" "example" {
bucket = "example"
}

resource "aws_s3_access_point" "example" {
bucket = aws_s3_bucket.example.id
bucket = aws_s3control_bucket.example.arn
name = "example"

# VPC must be specified for S3 on Outposts
vpc_configuration {
vpc_id = aws_vpc.example.id
}
Expand All @@ -52,15 +53,15 @@ resource "aws_vpc" "example" {

The following arguments are required:

* `bucket` - (Required) The name of the bucket that you want to associate this access point with.
* `bucket` - (Required) The name of an AWS Partition S3 Bucket or the Amazon Resource Name (ARN) of S3 on Outposts Bucket that you want to associate this access point with.
* `name` - (Required) The name you want to assign to this access point.

The following arguments are optional:

* `account_id` - (Optional) The AWS account ID for the owner of the bucket for which you want to create an access point. Defaults to automatically determined account ID of the Terraform AWS provider.
* `policy` - (Optional) A valid JSON document that specifies the policy that you want to apply to this access point.
* `public_access_block_configuration` - (Optional) Configuration block to manage the `PublicAccessBlock` configuration that you want to apply to this Amazon S3 bucket. You can enable the configuration options in any combination. Detailed below.
* `vpc_configuration` - (Optional) Configuration block to restrict access to this access point to requests from the specified Virtual Private Cloud (VPC). Detailed below.
* `vpc_configuration` - (Optional) Configuration block to restrict access to this access point to requests from the specified Virtual Private Cloud (VPC). Required for S3 on Outposts. Detailed below.

### public_access_block_configuration Configuration Block

Expand Down Expand Up @@ -91,13 +92,19 @@ In addition to all arguments above, the following attributes are exported:
* `domain_name` - The DNS domain name of the S3 Access Point in the format _`name`_-_`account_id`_.s3-accesspoint._region_.amazonaws.com.
Note: S3 access points only support secure access by HTTPS. HTTP isn't supported.
* `has_public_access_policy` - Indicates whether this access point currently has a policy that allows public access.
* `id` - AWS account ID and access point name separated by a colon (`:`).
* `id` - For Access Point of an AWS Partition S3 Bucket, the AWS account ID and access point name separated by a colon (`:`). For S3 on Outposts Bucket, the Amazon Resource Name (ARN) of the Access Point.
* `network_origin` - Indicates whether this access point allows access from the public Internet. Values are `VPC` (the access point doesn't allow access from the public Internet) and `Internet` (the access point allows access from the public Internet, subject to the access point and bucket access policies).

## Import

S3 Access Points can be imported using the `account_id` and `name` separated by a colon (`:`), e.g.
For Access Points associated with an AWS Partition S3 Bucket, this resource can be imported using the `account_id` and `name` separated by a colon (`:`), e.g.

```
$ terraform import aws_s3_access_point.example 123456789012:example
```

For Access Points associated with an S3 on Outposts Bucket, this resource can be imported using the Amazon Resource Name (ARN), e.g.

```
$ terraform import aws_s3_access_point.example arn:aws:s3-outposts:us-east-1:123456789012:outpost/op-1234567890123456/accesspoint/example
```