Skip to content

Commit

Permalink
fix: looks up principal ID, sso instance arn and permission set arn i…
Browse files Browse the repository at this point in the history
…nstead of inputs
  • Loading branch information
wanisfahmyDE committed Jan 10, 2025
1 parent 7b04d66 commit 4fbc53e
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 125 deletions.
52 changes: 2 additions & 50 deletions docs/resources/aws_account.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ Provides an AWS account resource via Control Tower.
## Example Usage

```terraform
# Basic Example
resource "controltower_aws_account" "basic_account_example" {
resource "controltower_aws_account" "account" {
name = "Example Account"
email = "aws-admin@example.com"
organizational_unit = "Sandbox"
Expand All @@ -27,51 +26,6 @@ resource "controltower_aws_account" "basic_account_example" {
email = "john.doe@example.com"
}
}
##########################################################################################################
# Extended Example to handle account reassignment upon update
locals {
username = "john.doe@example.de"
}
data "aws_ssoadmin_instances" "sso" {}
# Normally AWSAdministratorAccess is the default permission set assigned to the account upon creation by Control Tower.
data "aws_ssoadmin_permission_set" "permission" {
instance_arn = tolist(data.aws_ssoadmin_instances.sso.arns)[0]
name = "AWSAdministratorAccess"
}
data "aws_identitystore_user" "user" {
identity_store_id = tolist(data.aws_ssoadmin_instances.sso.identity_store_ids)[0]
alternate_identifier {
unique_attribute {
attribute_path = "UserName"
attribute_value = local.username
}
}
}
resource "controltower_aws_account" "extended_example_account" {
name = "Extended Example Account"
email = local.username
organizational_unit = "Sandbox"
organizational_unit_id_on_delete = "ou-some-id"
sso {
first_name = "John"
last_name = "Doe"
email = "aws-admin@example.com"
instance_arn = tolist(data.aws_ssoadmin_instances.sso.arns)[0]
principal_id = data.aws_identitystore_user.user.user_id
remove_account_assignment_on_update = true
permission_set_arn = data.aws_ssoadmin_permission_set.permission.arn
}
}
```

<!-- schema generated by tfplugindocs -->
Expand Down Expand Up @@ -108,7 +62,5 @@ Required:

Optional:

- `instance_arn` (String) ARN of the SSO instance. Required if remove_account_assignment_on_update is enabled.
- `permission_set_arn` (String) ARN of the permission set to be removed, normally it's the arn of AWSAdministratorAccess permission set. Required if remove_account_assignment_on_update is enabled.
- `principal_id` (String) Principal ID of the user. Required if remove_account_assignment_on_update is enabled.
- `permission_set_name` (String) Permission set name for the sso user. Defaults to AWSAdministratorAccess.
- `remove_account_assignment_on_update` (Boolean) If enabled, this will remove the account assignment for the old SSO user when the resource is updated.
50 changes: 2 additions & 48 deletions examples/resources/controltower_aws_account/resource.tf
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Basic Example
resource "controltower_aws_account" "basic_account_example" {
resource "controltower_aws_account" "account" {
name = "Example Account"
email = "aws-admin@example.com"
organizational_unit = "Sandbox"
Expand All @@ -11,49 +10,4 @@ resource "controltower_aws_account" "basic_account_example" {
last_name = "Doe"
email = "john.doe@example.com"
}
}
##########################################################################################################

# Extended Example to handle account reassignment upon update
locals {
username = "john.doe@example.de"
}

data "aws_ssoadmin_instances" "sso" {}

# Normally AWSAdministratorAccess is the default permission set assigned to the account upon creation by Control Tower.
data "aws_ssoadmin_permission_set" "permission" {

instance_arn = tolist(data.aws_ssoadmin_instances.sso.arns)[0]
name = "AWSAdministratorAccess"
}

data "aws_identitystore_user" "user" {

identity_store_id = tolist(data.aws_ssoadmin_instances.sso.identity_store_ids)[0]
alternate_identifier {
unique_attribute {
attribute_path = "UserName"
attribute_value = local.username
}
}
}

resource "controltower_aws_account" "extended_example_account" {
name = "Extended Example Account"
email = local.username
organizational_unit = "Sandbox"

organizational_unit_id_on_delete = "ou-some-id"

sso {
first_name = "John"
last_name = "Doe"
email = "aws-admin@example.com"
instance_arn = tolist(data.aws_ssoadmin_instances.sso.arns)[0]
principal_id = data.aws_identitystore_user.user.user_id
remove_account_assignment_on_update = true
permission_set_arn = data.aws_ssoadmin_permission_set.permission.arn

}
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
github.com/aws/aws-sdk-go-v2/service/identitystore v1.27.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.24.8 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26 h1:zXFLuEuMMUOvEARXFU
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.26/go.mod h1:3o2Wpy0bogG1kyOPrgkXA8pgIfEEv0+m19O9D5+W8y8=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
github.com/aws/aws-sdk-go-v2/service/identitystore v1.27.8 h1:Lg2UE1jqXgvhaWnHbnUuFdFORQLxKbJY4TSU87q6zGU=
github.com/aws/aws-sdk-go-v2/service/identitystore v1.27.8/go.mod h1:M5UW9CJQV78QiCxGihlGzwRbAD4B+fJf3y8yAij62Y0=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.7 h1:8eUsivBQzZHqe/3FE+cqwfH+0p5Jo8PFM/QYQSmeZ+M=
Expand Down
97 changes: 70 additions & 27 deletions internal/provider/resource_aws_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import (
"context"
"errors"
"fmt"
"github.com/aws/aws-sdk-go-v2/service/identitystore"
"github.com/aws/aws-sdk-go-v2/service/identitystore/document"

"github.com/aws/aws-sdk-go-v2/service/identitystore/types"
orgTypes "github.com/aws/aws-sdk-go-v2/service/organizations/types"
scTypes "github.com/aws/aws-sdk-go-v2/service/servicecatalog/types"
"github.com/aws/aws-sdk-go-v2/service/ssoadmin"

"regexp"
"sync"
"time"
Expand Down Expand Up @@ -75,29 +80,18 @@ func resourceAWSAccount() *schema.Resource {
Required: true,
ValidateFunc: validateEmailAddress,
},
"instance_arn": {
Description: "ARN of the SSO instance. Required if remove_account_assignment_on_update is enabled.",
Type: schema.TypeString,
Required: false,
Optional: true,
},
"remove_account_assignment_on_update": {
Description: "If enabled, this will remove the account assignment for the old SSO user when the resource is updated.",
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"principal_id": {
Description: "Principal ID of the user. Required if remove_account_assignment_on_update is enabled.",
Type: schema.TypeString,
Required: false,
Optional: true,
},
"permission_set_arn": {
Description: "ARN of the permission set to be removed, normally it's the arn of AWSAdministratorAccess permission set. Required if remove_account_assignment_on_update is enabled.",
"permission_set_name": {
Description: "Permission set name for the sso user. Defaults to AWSAdministratorAccess.",
Type: schema.TypeString,
Required: false,
Optional: true,
Default: "AWSAdministratorAccess",
},
},
},
Expand Down Expand Up @@ -438,37 +432,63 @@ func resourceAWSAccountUpdate(ctx context.Context, d *schema.ResourceData, m int
isRemoveAccountAssignmentOnUpdate := sso["remove_account_assignment_on_update"].(bool)

if d.HasChange("sso") && isRemoveAccountAssignmentOnUpdate {
ssoadmincon := ssoadmin.NewFromConfig(cfg)
ssoadminconn := ssoadmin.NewFromConfig(cfg)
identitystoreconn := identitystore.NewFromConfig(cfg)

accountId := d.Get("account_id").(string)
permissionSetName := sso["permission_set_name"].(string)

o, n := d.GetChange("sso")
if err := updateAccountAssignment(ctx, d, ssoadmincon, accountId, o, n); err != nil {
if err := updateAccountAssignment(ctx, ssoadminconn, identitystoreconn, accountId, permissionSetName, o, n); err != nil {
return diag.Errorf("error updating account assignment: %v", err)
}
}

return resourceAWSAccountRead(ctx, d, m)
}

func updateAccountAssignment(ctx context.Context, d *schema.ResourceData, ssoadmincon *ssoadmin.Client, accountId string, oldSSO interface{}, newSSO interface{}) error {
func updateAccountAssignment(ctx context.Context, ssoadminconn *ssoadmin.Client, identitystoreconn *identitystore.Client, accountId string, permissionSetName string, oldSSO interface{}, newSSO interface{}) error {

oldSSOMap := oldSSO.([]interface{})[0].(map[string]interface{})
newSSOMap := newSSO.([]interface{})[0].(map[string]interface{})
oldEmail := oldSSOMap["email"].(string)
newEmail := newSSOMap["email"].(string)
sso := d.Get("sso").([]interface{})[0].(map[string]interface{})
instanceArn := sso["instance_arn"].(string)
oldPrincipalId := oldSSOMap["principal_id"].(string)
newPrincipalId := newSSOMap["principal_id"].(string)
permissionSetArn := newSSOMap["permission_set_arn"].(string)

if oldEmail != newEmail && oldPrincipalId != newPrincipalId && instanceArn != "" && permissionSetArn != "" {
ssoInstances, err := ssoadminconn.ListInstances(ctx, &ssoadmin.ListInstancesInput{})
if err != nil {
return fmt.Errorf("error listing SSO instances: %v", err)
}
identityStoreId := ssoInstances.Instances[0].IdentityStoreId
instanceArn := ssoInstances.Instances[0].InstanceArn

alternateIdentifier := &types.AlternateIdentifierMemberUniqueAttribute{
Value: types.UniqueAttribute{
AttributePath: aws.String("UserName"),
AttributeValue: document.NewLazyDocument(oldEmail),
},
}

_, err := ssoadmincon.DeleteAccountAssignment(ctx, &ssoadmin.DeleteAccountAssignmentInput{
InstanceArn: &instanceArn,
principal, err := identitystoreconn.GetUserId(ctx, &identitystore.GetUserIdInput{
IdentityStoreId: identityStoreId,
AlternateIdentifier: alternateIdentifier,
})
if err != nil {
return fmt.Errorf("error getting principal id: %v", err)
}

permissionSetArn, err := findPermissionSetArn(ctx, ssoadminconn, instanceArn, permissionSetName)

if err != nil {
return fmt.Errorf("error finding permission set: %v", err)
}
if oldEmail != newEmail {

_, err := ssoadminconn.DeleteAccountAssignment(ctx, &ssoadmin.DeleteAccountAssignmentInput{
InstanceArn: instanceArn,
TargetId: &accountId,
TargetType: "AWS_ACCOUNT",
PrincipalType: "USER",
PrincipalId: &oldPrincipalId,
PrincipalId: principal.UserId,
PermissionSetArn: &permissionSetArn,
})
if err != nil {
Expand All @@ -477,7 +497,30 @@ func updateAccountAssignment(ctx context.Context, d *schema.ResourceData, ssoadm
}
return nil
}

func findPermissionSetArn(ctx context.Context, ssoadminconn *ssoadmin.Client, instanceArn *string, permissionSetName string) (string, error) {
paginator := ssoadmin.NewListPermissionSetsPaginator(ssoadminconn, &ssoadmin.ListPermissionSetsInput{
InstanceArn: instanceArn,
})
for paginator.HasMorePages() {
output, err := paginator.NextPage(ctx)
if err != nil {
return "", fmt.Errorf("error listing permission sets: %w", err)
}
for _, permissionSetArn := range output.PermissionSets {
describeInput, err := ssoadminconn.DescribePermissionSet(ctx, &ssoadmin.DescribePermissionSetInput{
InstanceArn: instanceArn,
PermissionSetArn: &permissionSetArn,
})
if err != nil {
return "", fmt.Errorf("error describing permission set: %w", err)
}
if *describeInput.PermissionSet.Name == permissionSetName {
return permissionSetArn, nil
}
}
}
return "", fmt.Errorf("permission set not found")
}
func resourceAWSAccountDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
cfg := m.(aws.Config)

Expand Down

0 comments on commit 4fbc53e

Please sign in to comment.