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 ExternalID support to AWS Auth STS configuration #26628

Merged
merged 9 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
10 changes: 8 additions & 2 deletions builtin/credential/aws/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,7 @@ This is an acceptance test.
export TEST_AWS_EC2_IAM_ROLE_ARN=$(aws iam get-role --role-name $(curl -q http://169.254.169.254/latest/meta-data/iam/security-credentials/ -S -s) --query Role.Arn --output text)
export TEST_AWS_EC2_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)


If the test is not being run on an EC2 instance that has access to
credentials using EC2RoleProvider, on top of the above vars, following
needs to be set:
Expand Down Expand Up @@ -1407,6 +1408,11 @@ func TestBackend_pathStsConfig(t *testing.T) {
"sts_role": "arn:aws:iam:account1:role/myRole",
}

data2 := map[string]interface{}{
"sts_role": "arn:aws:iam:account1:role/myRole",
"external_id": "fake_id",
}

stsReq.Data = data
// test create operation
resp, err := b.HandleRequest(context.Background(), stsReq)
Expand Down Expand Up @@ -1440,8 +1446,8 @@ func TestBackend_pathStsConfig(t *testing.T) {

stsReq.Operation = logical.CreateOperation
stsReq.Path = "config/sts/account2"
stsReq.Data = data
// create another entry to test the list operation
stsReq.Data = data2
// create another entry with alternate data to test ExternalID and LIST
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, could we add a block for checking "external_id": "fake_id" existence in a read response before line 1455?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a response check for data2

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

resp, err = b.HandleRequest(context.Background(), stsReq)
if err != nil || (resp != nil && resp.IsError()) {
t.Fatal(err)
Expand Down
20 changes: 10 additions & 10 deletions builtin/credential/aws/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (b *backend) getRawClientConfig(ctx context.Context, s logical.Storage, reg
// It uses getRawClientConfig to obtain config for the runtime environment, and if
// stsRole is a non-empty string, it will use AssumeRole to obtain a set of assumed
// credentials. The credentials will expire after 15 minutes but will auto-refresh.
func (b *backend) getClientConfig(ctx context.Context, s logical.Storage, region, stsRole, accountID, clientType string) (*aws.Config, error) {
func (b *backend) getClientConfig(ctx context.Context, s logical.Storage, region, stsRole, externalID, accountID, clientType string) (*aws.Config, error) {
config, err := b.getRawClientConfig(ctx, s, region, clientType)
if err != nil {
return nil, err
Expand All @@ -105,7 +105,7 @@ func (b *backend) getClientConfig(ctx context.Context, s logical.Storage, region
if err != nil {
return nil, err
}
assumedCredentials := stscreds.NewCredentials(sess, stsRole)
assumedCredentials := stscreds.NewCredentials(sess, stsRole, func(p *stscreds.AssumeRoleProvider) { p.ExternalID = aws.String(externalID) })
// Test that we actually have permissions to assume the role
if _, err = assumedCredentials.Get(); err != nil {
return nil, err
Expand Down Expand Up @@ -180,22 +180,22 @@ func (b *backend) setCachedUserId(userId, arn string) {
}
}

func (b *backend) stsRoleForAccount(ctx context.Context, s logical.Storage, accountID string) (string, error) {
func (b *backend) stsRoleForAccount(ctx context.Context, s logical.Storage, accountID string) (string, string, error) {
// Check if an STS configuration exists for the AWS account
sts, err := b.lockedAwsStsEntry(ctx, s, accountID)
if err != nil {
return "", fmt.Errorf("error fetching STS config for account ID %q: %w", accountID, err)
return "", "", fmt.Errorf("error fetching STS config for account ID %q: %w", accountID, err)
}
// An empty STS role signifies the master account
if sts != nil {
return sts.StsRole, nil
return sts.StsRole, sts.ExternalID, nil
}
return "", nil
return "", "", nil
}

// clientEC2 creates a client to interact with AWS EC2 API
func (b *backend) clientEC2(ctx context.Context, s logical.Storage, region, accountID string) (*ec2.EC2, error) {
stsRole, err := b.stsRoleForAccount(ctx, s, accountID)
stsRole, stsExternalID, err := b.stsRoleForAccount(ctx, s, accountID)
if err != nil {
return nil, err
}
Expand All @@ -218,7 +218,7 @@ func (b *backend) clientEC2(ctx context.Context, s logical.Storage, region, acco

// Create an AWS config object using a chain of providers
var awsConfig *aws.Config
awsConfig, err = b.getClientConfig(ctx, s, region, stsRole, accountID, "ec2")
awsConfig, err = b.getClientConfig(ctx, s, region, stsRole, stsExternalID, accountID, "ec2")
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -247,7 +247,7 @@ func (b *backend) clientEC2(ctx context.Context, s logical.Storage, region, acco

// clientIAM creates a client to interact with AWS IAM API
func (b *backend) clientIAM(ctx context.Context, s logical.Storage, region, accountID string) (*iam.IAM, error) {
stsRole, err := b.stsRoleForAccount(ctx, s, accountID)
stsRole, stsExternalID, err := b.stsRoleForAccount(ctx, s, accountID)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -277,7 +277,7 @@ func (b *backend) clientIAM(ctx context.Context, s logical.Storage, region, acco

// Create an AWS config object using a chain of providers
var awsConfig *aws.Config
awsConfig, err = b.getClientConfig(ctx, s, region, stsRole, accountID, "iam")
awsConfig, err = b.getClientConfig(ctx, s, region, stsRole, stsExternalID, accountID, "iam")
if err != nil {
return nil, err
}
Expand Down
26 changes: 22 additions & 4 deletions builtin/credential/aws/path_config_sts.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import (

// awsStsEntry is used to store details of an STS role for assumption
type awsStsEntry struct {
StsRole string `json:"sts_role"`
StsRole string `json:"sts_role"`
ExternalID string `json:"external_id,omitempty"` // optional, but recommended
}

func (b *backend) pathListSts() *framework.Path {
Expand Down Expand Up @@ -57,6 +58,11 @@ instances in this account.`,
Description: `AWS ARN for STS role to be assumed when interacting with the account specified.
The Vault server must have permissions to assume this role.`,
},
"external_id": {
Type: framework.TypeString,
Description: `AWS external ID to be used when assuming the STS role.`,
Required: false,
},
},

ExistenceCheck: b.pathConfigStsExistenceCheck,
Expand Down Expand Up @@ -192,10 +198,15 @@ func (b *backend) pathConfigStsRead(ctx context.Context, req *logical.Request, d
return nil, nil
}

dt := map[string]interface{}{
"sts_role": stsEntry.StsRole,
}
if stsEntry.ExternalID != "" {
dt["external_id"] = stsEntry.ExternalID
}

return &logical.Response{
Data: map[string]interface{}{
"sts_role": stsEntry.StsRole,
},
Data: dt,
}, nil
}

Expand Down Expand Up @@ -230,6 +241,13 @@ func (b *backend) pathConfigStsCreateUpdate(ctx context.Context, req *logical.Re
return logical.ErrorResponse("sts role cannot be empty"), nil
}

stsExternalID, ok := data.GetOk("external_id")
if ok {
stsEntry.ExternalID = stsExternalID.(string)
}

b.Logger().Info("setting sts", "account_id", accountID, "sts_role", stsEntry.StsRole, "external_id", stsEntry.ExternalID)

// save the provided STS role
if err := b.nonLockedSetAwsStsEntry(ctx, req.Storage, accountID, stsEntry); err != nil {
return nil, err
Expand Down
3 changes: 3 additions & 0 deletions changelog/26628.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
auth/aws: add support for external_ids in AWS assume-role
```
2 changes: 1 addition & 1 deletion helper/forwarding/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion helper/identity/mfa/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion helper/identity/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion helper/storagepacker/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion physical/raft/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/database/dbplugin/database.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/database/dbplugin/v5/proto/database.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/helper/clientcountutil/generation/generate_data.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/helper/pluginutil/multiplexing.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/logical/event.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/logical/identity.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/logical/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/logical/version.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/plugin/pb/backend.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault/activity/activity_log.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault/hcp_link/proto/link_control/link_control.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault/hcp_link/proto/meta/meta.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault/hcp_link/proto/node_status/status.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault/request_forwarding_service.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault/seal/multi_wrap_value.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion vault/tokens/token.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions website/content/api-docs/auth/aws.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,9 @@ when validating IAM principals or EC2 instances in the particular AWS account.
- `sts_role` `(string: <required>)` - AWS ARN for STS role to be assumed when
interacting with the account specified. The Vault server must have
permissions to assume this role.
- `external_id` `(string: "")` - External ID to be used when assuming
the STS role. If set, the STS role must be configured to require this
external ID.
kpcraig marked this conversation as resolved.
Show resolved Hide resolved

### Sample payload

Expand Down
Loading