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

Add support for Active Directory in AWS Transfer #20342

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
97ed8a1
Start implementation of transfer access and transfer server active di…
toriath Jul 20, 2021
b43a881
formatting
toriath Jul 20, 2021
33e2d40
add test for transfer server with directory service
toriath Jul 20, 2021
7562b15
Create test file for transfer accesses and start implementation
toriath Jul 20, 2021
4036666
tests for transfer accesses
toriath Jul 20, 2021
c67d12c
fix up todos
toriath Jul 22, 2021
bf90c7f
fixes
toriath Jul 22, 2021
299853b
format
toriath Jul 26, 2021
98317db
Merge branch 'main' into feature/transfer-server_active-directory
toriath Jul 26, 2021
adc5fc4
resolve issues
toriath Jul 26, 2021
c16eb80
fix config
toriath Jul 26, 2021
96634c0
Fix up tests
toriath Jul 26, 2021
12bf762
fix tests and bugs
toriath Jul 27, 2021
ec07d69
fix test data
toriath Jul 28, 2021
45ea8c6
fix imports
toriath Jul 28, 2021
e1e40a8
Fix IDs missing on access update
toriath Jul 28, 2021
7caa878
fix fmt
toriath Jul 28, 2021
54628f3
Fix changed SID in access test
toriath Jul 28, 2021
c1dad17
add changelog entry
toriath Jul 28, 2021
90ef446
Fix role 'being changed' without role changing
toriath Jul 28, 2021
00a6bb5
Merge branch 'main' into f-aws_transfer_server_directory_service
toriath Aug 2, 2021
b624ac6
basic test for efs and s3 scope down policy
toriath Aug 6, 2021
1cbc35c
Extend documentation
toriath Aug 6, 2021
5d17268
refactor efs basic test
toriath Aug 9, 2021
0d90ae0
fix terraform formatting in documentation
toriath Aug 19, 2021
0db70cd
replace static arn partition with aws_partition datasource
toriath Aug 19, 2021
2d695e7
remove deprecated interpolation only expressions
toriath Aug 19, 2021
d7d7a9f
terraform formatting
toriath Aug 19, 2021
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
7 changes: 7 additions & 0 deletions .changelog/20342.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:enhancement
resource/aws_transfer_server: Add directory_id argument
```

```release-note:new-resource
aws_transfer_access
```
29 changes: 29 additions & 0 deletions aws/internal/service/transfer/finder/finder.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,32 @@ func UserByID(conn *transfer.Transfer, serverId, userName string) (*transfer.Des

return output, nil
}

func AccessByID(conn *transfer.Transfer, serverId, externalId string) (*transfer.DescribeAccessOutput, error) {
input := &transfer.DescribeAccessInput{
ExternalId: aws.String(externalId),
ServerId: aws.String(serverId),
}

output, err := conn.DescribeAccess(input)

if tfawserr.ErrCodeEquals(err, transfer.ErrCodeResourceNotFoundException) {
return nil, &resource.NotFoundError{
LastError: err,
LastRequest: input,
}
}

if err != nil {
return nil, err
}

if output == nil || output.Access == nil {
return nil, &resource.NotFoundError{
Message: "Empty result",
LastRequest: input,
}
}

return output, nil
}
25 changes: 21 additions & 4 deletions aws/internal/service/transfer/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,38 @@ import (
"strings"
)

const userResourceIDSeparator = "/"
const transferResourceIDSeparator = "/"

func UserCreateResourceID(serverID, userName string) string {
parts := []string{serverID, userName}
id := strings.Join(parts, userResourceIDSeparator)
id := strings.Join(parts, transferResourceIDSeparator)

return id
}

func UserParseResourceID(id string) (string, string, error) {
parts := strings.Split(id, userResourceIDSeparator)
parts := strings.Split(id, transferResourceIDSeparator)

if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
return parts[0], parts[1], nil
}

return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected SERVERID%[2]sUSERNAME", id, userResourceIDSeparator)
return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected SERVERID%[2]sUSERNAME", id, transferResourceIDSeparator)
}

func AccessCreateResourceID(serverID, externalID string) string {
parts := []string{serverID, externalID}
id := strings.Join(parts, transferResourceIDSeparator)

return id
}

func AccessParseResourceID(id string) (string, string, error) {
parts := strings.Split(id, transferResourceIDSeparator)

if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
return parts[0], parts[1], nil
}

return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected SERVERID%[2]sEXTERNALID", id, transferResourceIDSeparator)
}
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -1133,6 +1133,7 @@ func Provider() *schema.Provider {
"aws_timestreamwrite_database": resourceAwsTimestreamWriteDatabase(),
"aws_timestreamwrite_table": resourceAwsTimestreamWriteTable(),
"aws_transfer_server": resourceAwsTransferServer(),
"aws_transfer_access": resourceAwsTransferAccess(),
"aws_transfer_ssh_key": resourceAwsTransferSshKey(),
"aws_transfer_user": resourceAwsTransferUser(),
"aws_volume_attachment": resourceAwsVolumeAttachment(),
Expand Down
297 changes: 297 additions & 0 deletions aws/resource_aws_transfer_access.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package aws

import (
"fmt"
"log"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/transfer"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
tftransfer "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/transfer"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/service/transfer/finder"
"github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource"
)

func resourceAwsTransferAccess() *schema.Resource {
return &schema.Resource{
Create: resourceAwsTransferAccessCreate,
Read: resourceAwsTransferAccessRead,
Update: resourceAwsTransferAccessUpdate,
Delete: resourceAwsTransferAccessDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"external_id": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(1, 256),
},

"home_directory": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringLenBetween(0, 1024),
},

"home_directory_mappings": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"entry": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(0, 1024),
},
"target": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringLenBetween(0, 1024),
},
},
},
},

"home_directory_type": {
Type: schema.TypeString,
Optional: true,
Default: transfer.HomeDirectoryTypePath,
ValidateFunc: validation.StringInSlice([]string{transfer.HomeDirectoryTypePath, transfer.HomeDirectoryTypeLogical}, false),
},

"policy": {
Type: schema.TypeString,
Optional: true,
},

"posix_profile": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"gid": {
Type: schema.TypeInt,
Required: true,
},
"uid": {
Type: schema.TypeInt,
Required: true,
},
"secondary_gids": {
Type: schema.TypeSet,
Elem: &schema.Schema{Type: schema.TypeInt},
Optional: true,
},
},
},
},

"role": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validateArn,
},

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

"force_destroy": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
},
}
}

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

input := &transfer.CreateAccessInput{}

serverID := d.Get("server_id").(string)
externalID := d.Get("external_id").(string)

input.ServerId = aws.String(serverID)
input.ExternalId = aws.String(externalID)

if v, ok := d.GetOk("home_directory"); ok {
input.HomeDirectory = aws.String(v.(string))
}

if v, ok := d.GetOk("home_directory_mappings"); ok {
input.HomeDirectoryMappings = expandAwsTransferHomeDirectoryMappings(v.([]interface{}))
}

if v, ok := d.GetOk("home_directory_type"); ok {
input.HomeDirectoryType = aws.String(v.(string))
}

if v, ok := d.GetOk("policy"); ok {
input.Policy = aws.String(v.(string))
}

if v, ok := d.GetOk("posix_profile"); ok {
input.PosixProfile = expandTransferUserPosixUser(v.([]interface{}))
}

if v, ok := d.GetOk("role"); ok {
input.Role = aws.String(v.(string))
}

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

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

d.SetId(tftransfer.AccessCreateResourceID(*output.ServerId, *output.ExternalId))

return resourceAwsTransferAccessRead(d, meta)
}

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

serverId, externalId, err := tftransfer.AccessParseResourceID(d.Id())
if err != nil {
return fmt.Errorf("error parsing Transfer Access ID: %s", err)
}

output, err := finder.AccessByID(conn, serverId, externalId)

if !d.IsNewResource() && tfresource.NotFound(err) {
log.Printf("[WARN] Access with external ID (%s) not found for server (%s), removing from state", externalId, serverId)
d.SetId("")
return nil
}

if err != nil {
return fmt.Errorf("error reading Access with external ID (%s) for server (%s): %w", externalId, serverId, err)
}

access := output.Access

if err := d.Set("external_id", access.ExternalId); err != nil {
return fmt.Errorf("Error setting external_id: %w", err)
}
if err := d.Set("server_id", serverId); err != nil {
return fmt.Errorf("Error setting server_id: %w", err)
}
if err := d.Set("policy", access.Policy); err != nil {
return fmt.Errorf("Error setting policy: %w", err)
}
if err := d.Set("posix_profile", flattenTransferUserPosixUser(access.PosixProfile)); err != nil {
return fmt.Errorf("Error setting posix_profile: %w", err)
}
if err := d.Set("home_directory_type", access.HomeDirectoryType); err != nil {
return fmt.Errorf("Error setting home_directory_type: %w", err)
}
if err := d.Set("home_directory", access.HomeDirectory); err != nil {
return fmt.Errorf("Error setting home_directory: %w", err)
}
if err := d.Set("role", access.Role); err != nil {
return fmt.Errorf("Error setting role: %w", err)
}

if err := d.Set("home_directory_mappings", flattenAwsTransferHomeDirectoryMappings(access.HomeDirectoryMappings)); err != nil {
return fmt.Errorf("Error setting home_directory_mappings: %w", err)
}

if err := d.Set("posix_profile", flattenTransferUserPosixUser(access.PosixProfile)); err != nil {
return fmt.Errorf("Error setting posix_profile: %w", err)
}

return nil
}

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

serverId, externalId, err := tftransfer.AccessParseResourceID(d.Id())
if err != nil {
return fmt.Errorf("error parsing Transfer Access ID: %s", err)
}

input := &transfer.UpdateAccessInput{
ServerId: aws.String(serverId),
ExternalId: aws.String(externalId),
}

hasChanges := false

if d.HasChange("home_directory") {
input.HomeDirectory = aws.String(d.Get("home_directory").(string))
hasChanges = true
}

if d.HasChange("home_directory_mappings") {
input.HomeDirectoryMappings = expandAwsTransferHomeDirectoryMappings(d.Get("home_directory_mappings").([]interface{}))
hasChanges = true
}

if d.HasChange("home_directory_type") {
input.HomeDirectoryType = aws.String(d.Get("home_directory_type").(string))
hasChanges = true
}

if d.HasChange("policy") {
input.Policy = aws.String(d.Get("policy").(string))
hasChanges = true
}

if d.HasChange("posix_profile") {
input.PosixProfile = expandTransferUserPosixUser(d.Get("posix_profile").([]interface{}))
hasChanges = true
}

if d.HasChange("role") {
input.Role = aws.String(d.Get("role").(string))
hasChanges = true
}

if hasChanges {
log.Printf("[DEBUG] Updating Transfer Access: %s", input)
_, err := conn.UpdateAccess(input)
if err != nil {
return fmt.Errorf("error updating Transfer Access (externalID: %s, serverID: %s): %w", aws.StringValue(input.ExternalId), aws.StringValue(input.ServerId), err)
}
}

return resourceAwsTransferAccessRead(d, meta)
}

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

serverId, externalId, err := tftransfer.AccessParseResourceID(d.Id())
if err != nil {
return fmt.Errorf("error parsing Transfer Access ID: %s", err)
}

log.Printf("[DEBUG] Deleting Transfer Access: (externalID: %s, serverID: %s)", externalId, serverId)
_, err = conn.DeleteAccess(&transfer.DeleteAccessInput{
ExternalId: aws.String(externalId),
ServerId: aws.String(serverId),
})

if tfawserr.ErrCodeEquals(err, transfer.ErrCodeResourceNotFoundException) {
return nil
}

if err != nil {
return fmt.Errorf("error deleting Transfer Access (externalID: %s, serverID: %s): %w", externalId, serverId, err)
}

return nil
}
Loading