Skip to content

Commit

Permalink
Merge pull request #32654 from CalvinE/f-aws_transfer_server_structur…
Browse files Browse the repository at this point in the history
…ed_logging_destinations

F aws transfer server structured logging destinations
  • Loading branch information
ewbankkit committed Jul 25, 2023
2 parents ec2a8ba + 70487f3 commit a98f3c6
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 10 deletions.
7 changes: 7 additions & 0 deletions .changelog/32654.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_transfer_server: Add `structured_log_destinations` argument
```

```release-note:enhancement
data-source/aws_transfer_server: Add `structured_log_destinations` attribute
```
19 changes: 19 additions & 0 deletions internal/service/transfer/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,15 @@ func ResourceServer() *schema.Resource {
Default: SecurityPolicyName2018_11,
ValidateFunc: validation.StringInSlice(SecurityPolicyName_Values(), false),
},
"structured_log_destinations": {
Type: schema.TypeSet,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: verify.ValidARN,
},
Description: "This is a set of arns of destinations that will receive structured logs from the transfer server",
Optional: true,
},
names.AttrTags: tftags.TagsSchema(),
names.AttrTagsAll: tftags.TagsSchemaComputed(),
"url": {
Expand Down Expand Up @@ -371,6 +380,10 @@ func resourceServerCreate(ctx context.Context, d *schema.ResourceData, meta inte
input.SecurityPolicyName = aws.String(v.(string))
}

if v, ok := d.GetOk("structured_log_destinations"); ok && v.(*schema.Set).Len() > 0 {
input.StructuredLogDestinations = flex.ExpandStringSet(v.(*schema.Set))
}

if v, ok := d.GetOk("url"); ok {
if input.IdentityProviderDetails == nil {
input.IdentityProviderDetails = &transfer.IdentityProviderDetails{}
Expand Down Expand Up @@ -489,6 +502,7 @@ func resourceServerRead(ctx context.Context, d *schema.ResourceData, meta interf
}
d.Set("protocols", aws.StringValueSlice(output.Protocols))
d.Set("security_policy_name", output.SecurityPolicyName)
d.Set("structured_log_destinations", aws.StringValueSlice(output.StructuredLogDestinations))
if output.IdentityProviderDetails != nil {
d.Set("url", output.IdentityProviderDetails.Url)
} else {
Expand Down Expand Up @@ -666,6 +680,11 @@ func resourceServerUpdate(ctx context.Context, d *schema.ResourceData, meta inte
input.SecurityPolicyName = aws.String(d.Get("security_policy_name").(string))
}

// Per the docs it does not matter if this field has changed,
// if the update passes this as empty the structured logging will be turned off,
// so we need to always pass the new.
input.StructuredLogDestinations = flex.ExpandStringSet(d.Get("structured_log_destinations").(*schema.Set))

if d.HasChange("workflow_details") {
input.WorkflowDetails = expandWorkflowDetails(d.Get("workflow_details").([]interface{}))
}
Expand Down
18 changes: 8 additions & 10 deletions internal/service/transfer/server_data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ func DataSourceServer() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},

"certificate": {
Type: schema.TypeString,
Computed: true,
Expand All @@ -31,50 +30,48 @@ func DataSourceServer() *schema.Resource {
Type: schema.TypeString,
Computed: true,
},

"endpoint": {
Type: schema.TypeString,
Computed: true,
},

"endpoint_type": {
Type: schema.TypeString,
Computed: true,
},

"identity_provider_type": {
Type: schema.TypeString,
Computed: true,
},

"invocation_role": {
Type: schema.TypeString,
Computed: true,
},

"logging_role": {
Type: schema.TypeString,
Computed: true,
},

"protocols": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},

"security_policy_name": {
Type: schema.TypeString,
Computed: true,
},

"server_id": {
Type: schema.TypeString,
Required: true,
},

"structured_log_destinations": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"url": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -112,6 +109,7 @@ func dataSourceServerRead(ctx context.Context, d *schema.ResourceData, meta inte
d.Set("logging_role", output.LoggingRole)
d.Set("protocols", aws.StringValueSlice(output.Protocols))
d.Set("security_policy_name", output.SecurityPolicyName)
d.Set("structured_log_destinations", aws.StringValueSlice(output.StructuredLogDestinations))
if output.IdentityProviderDetails != nil {
d.Set("url", output.IdentityProviderDetails.Url)
} else {
Expand Down
1 change: 1 addition & 0 deletions internal/service/transfer/server_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func testAccServerDataSource_basic(t *testing.T) {
resource.TestCheckResourceAttrPair(datasourceName, "endpoint", resourceName, "endpoint"),
resource.TestCheckResourceAttrPair(datasourceName, "identity_provider_type", resourceName, "identity_provider_type"),
resource.TestCheckResourceAttrPair(datasourceName, "logging_role", resourceName, "logging_role"),
resource.TestCheckResourceAttrPair(datasourceName, "structured_log_destinations.#", resourceName, "structured_log_destinations.#"),
),
},
},
Expand Down
154 changes: 154 additions & 0 deletions internal/service/transfer/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package transfer_test

import (
"context"
"errors"
"fmt"
"regexp"
"testing"
Expand Down Expand Up @@ -71,6 +72,7 @@ func testAccServer_basic(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "protocols.#", "1"),
resource.TestCheckTypeSetElemAttr(resourceName, "protocols.*", "SFTP"),
resource.TestCheckResourceAttr(resourceName, "security_policy_name", "TransferSecurityPolicy-2018-11"),
resource.TestCheckResourceAttr(resourceName, "structured_log_destinations.#", "0"),
resource.TestCheckResourceAttr(resourceName, "tags.%", "0"),
resource.TestCheckResourceAttr(resourceName, "url", ""),
resource.TestCheckResourceAttr(resourceName, "workflow_details.#", "0"),
Expand Down Expand Up @@ -740,6 +742,46 @@ func testAccServer_updateEndpointType_vpcToPublic(t *testing.T) {
})
}

func testAccServer_structuredLogDestinations(t *testing.T) {
ctx := acctest.Context(t)
var s transfer.DescribedServer
resourceName := "aws_transfer_server.test"
cloudwatchLogGroupName := "aws_cloudwatch_log_group.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t); testAccPreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, transfer.EndpointsID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckServerDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccServerConfig_structuredLogDestinations(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckServerExists(ctx, resourceName, &s),
// resource.TestCheckTypeSetElemAttr(resourceName, "structured_logging_destinations.*", *s.StructuredLogDestinations[0]),
resource.ComposeTestCheckFunc(testAccServerCheck_structuredLogDestinations(resourceName, cloudwatchLogGroupName)),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"force_destroy"},
},
{
Config: testAccServerConfig_structuredLogDestinationsUpdate(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckServerExists(ctx, resourceName, &s),
// resource.TestCheckTypeSetElemAttr(resourceName, "structured_logging_destinations.*", *s.StructuredLogDestinations[0]),
// resource.TestCheckTypeSetElemAttr(resourceName, "structured_logging_destinations.*", fmt.Sprintf("\"${%s.arn}:*\"", cloudwatchLogGroupName)),
resource.ComposeTestCheckFunc(testAccServerCheck_structuredLogDestinations(resourceName, cloudwatchLogGroupName)),
),
},
},
})
}

func testAccServer_protocols(t *testing.T) {
ctx := acctest.Context(t)
var s transfer.DescribedServer
Expand Down Expand Up @@ -1219,6 +1261,32 @@ func testAccCheckServerDestroy(ctx context.Context) resource.TestCheckFunc {
}
}

func testAccServerCheck_structuredLogDestinations(resourceName, cloudwatchLogGroupName string) func(s *terraform.State) error {
return func(s *terraform.State) error {
cwResource, ok := s.RootModule().Resources[cloudwatchLogGroupName]
if !ok {
return fmt.Errorf("resource not found: %s", cloudwatchLogGroupName)
}
cwARN, ok := cwResource.Primary.Attributes["arn"]
if !ok {
return errors.New("cloudwatch group arn missing")
}
expectedSLD := fmt.Sprintf("%s:*", cwARN)
transferServerResource, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("resource not found: %s", resourceName)
}
slds, ok := transferServerResource.Primary.Attributes["structured_log_destinations.0"]
if !ok {
return errors.New("transfer server structured logging destinations missing")
}
if expectedSLD != slds {
return fmt.Errorf("'%s' != '%s'", expectedSLD, slds)
}
return nil
}
}

func testAccServerConfig_vpcBase(rName string) string {
return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(`
resource "aws_vpc" "test" {
Expand Down Expand Up @@ -1784,6 +1852,92 @@ resource "aws_transfer_server" "test" {
`, rName, hostKey)
}

func testAccServerConfig_structuredLogDestinationsBase(rName string) string {
return fmt.Sprintf(`
resource "aws_cloudwatch_log_group" "test" {
name = %[1]q
}
data "aws_iam_policy_document" "test" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["transfer.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_policy" "test" {
name = %[1]q
policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Action" : [
"logs:CreateLogStream",
"logs:DescribeLogStreams",
"logs:CreateLogGroup",
"logs:PutLogEvents"
],
"Resource" : "*"
}
]
})
}
resource "aws_iam_role" "test" {
name = %[1]q
assume_role_policy = data.aws_iam_policy_document.test.json
}
resource "aws_iam_role_policy_attachment" "test" {
role = aws_iam_role.test.name
policy_arn = aws_iam_policy.test.arn
}
`, rName)
}

func testAccServerConfig_structuredLogDestinations(rName string) string {
return acctest.ConfigCompose(testAccServerConfig_structuredLogDestinationsBase(rName), fmt.Sprintf(`
resource "aws_transfer_server" "test" {
endpoint_type = "PUBLIC"
logging_role = aws_iam_role.test.arn
protocols = ["SFTP"]
structured_log_destinations = [
"${aws_cloudwatch_log_group.test.arn}:*"
]
tags = {
Name = %[1]q
}
}
`, rName))
}

func testAccServerConfig_structuredLogDestinationsUpdate(rName string) string {
return acctest.ConfigCompose(testAccServerConfig_structuredLogDestinationsBase(rName), fmt.Sprintf(`
resource "aws_transfer_server" "test" {
endpoint_type = "PUBLIC"
logging_role = aws_iam_role.test.arn
protocols = ["SFTP"]
structured_log_destinations = [
"${aws_cloudwatch_log_group.test.arn}:*"
]
pre_authentication_login_banner = "This system is for the use of authorized users only - pre"
post_authentication_login_banner = "This system is for the use of authorized users only - post"
tags = {
Name = %[1]q
}
}
`, rName))
}

func testAccServerConfig_protocols(rName string) string {
return acctest.ConfigCompose(
testAccServerConfig_vpcBase(rName),
Expand Down
1 change: 1 addition & 0 deletions internal/service/transfer/transfer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func TestAccTransfer_serial(t *testing.T) {
"Protocols": testAccServer_protocols,
"ProtocolDetails": testAccServer_protocolDetails,
"SecurityPolicy": testAccServer_securityPolicy,
"StructuredLogDestinations": testAccServer_structuredLogDestinations,
"UpdateEndpointTypePublicToVPC": testAccServer_updateEndpointType_publicToVPC,
"UpdateEndpointTypePublicToVPCAddressAllocationIDs": testAccServer_updateEndpointType_publicToVPC_addressAllocationIDs,
"UpdateEndpointTypeVPCEndpointToVPC": testAccServer_updateEndpointType_vpcEndpointToVPC,
Expand Down
1 change: 1 addition & 0 deletions website/docs/d/transfer_server.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ This data source exports the following attributes in addition to the arguments a
* `logging_role` - ARN of an IAM role that allows the service to write your SFTP users’ activity to your Amazon CloudWatch logs for monitoring and auditing purposes.
* `protocols` - File transfer protocol or protocols over which your file transfer protocol client can connect to your server's endpoint.
* `security_policy_name` - The name of the security policy that is attached to the server.
* `structured_logging_destinations` - A set of ARNs of destinations that will receive structured logs from the transfer server such as CloudWatch Log Group ARNs.
* `url` - URL of the service endpoint used to authenticate users with an `identity_provider_type` of `API_GATEWAY`.
37 changes: 37 additions & 0 deletions website/docs/r/transfer_server.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,42 @@ resource "aws_transfer_server" "example" {
}
```

### Using Structured Logging Destinations

```terraform
resource "aws_cloudwatch_log_group" "transfer" {
name_prefix = "transfer_test_"
}
data "aws_iam_policy_document" "transfer_assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["transfer.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
resource "aws_iam_role" "iam_for_transfer" {
name_prefix = "iam_for_transfer_"
assume_role_policy = data.aws_iam_policy_document.transfer_assume_role.json
managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSTransferLoggingAccess"]
}
resource "aws_transfer_server" "transfer" {
endpoint_type = "PUBLIC"
logging_role = aws_iam_role.iam_for_transfer.arn
protocols = ["SFTP"]
structured_log_destinations = [
"${aws_cloudwatch_log_group.transfer.arn}:*"
]
}
```

## Argument Reference

This resource supports the following arguments:
Expand All @@ -110,6 +146,7 @@ This resource supports the following arguments:
* `pre_authentication_login_banner`- (Optional) Specify a string to display when users connect to a server. This string is displayed before the user authenticates.
* `protocol_details`- (Optional) The protocol settings that are configured for your server.
* `security_policy_name` - (Optional) Specifies the name of the security policy that is attached to the server. Possible values are `TransferSecurityPolicy-2018-11`, `TransferSecurityPolicy-2020-06`, `TransferSecurityPolicy-FIPS-2020-06`, `TransferSecurityPolicy-2022-03` and `TransferSecurityPolicy-2023-05`. Default value is: `TransferSecurityPolicy-2018-11`.
* `structured_logging_destinations` - (Optional) A set of ARNs of destinations that will receive structured logs from the transfer server such as CloudWatch Log Group ARNs. If provided this enables the transfer server to emit structured logs to the specified locations.
* `tags` - (Optional) A map of tags to assign to the resource. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level.
* `workflow_details` - (Optional) Specifies the workflow details. See Workflow Details below.

Expand Down

0 comments on commit a98f3c6

Please sign in to comment.