Skip to content

Commit 688693c

Browse files
authored
Merge pull request #1170 from hashicorp/f-role-chaining
Adds IAM role chaining
2 parents 3ec7b78 + 8516da5 commit 688693c

File tree

8 files changed

+394
-422
lines changed

8 files changed

+394
-422
lines changed

aws_config_test.go

Lines changed: 155 additions & 158 deletions
Large diffs are not rendered by default.

credentials.go

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ AWS Error: %w`, err)
100100
return nil, "", diags.Append(c.NewNoValidCredentialSourcesError(err))
101101
}
102102

103-
if c.AssumeRole == nil {
103+
if len(c.AssumeRole) == 0 {
104104
return cfg.Credentials, creds.Source, diags
105105
}
106106

@@ -157,67 +157,73 @@ func assumeRoleCredentialsProvider(ctx context.Context, awsConfig aws.Config, c
157157

158158
logger := logging.RetrieveLogger(ctx)
159159

160-
ar := c.AssumeRole
160+
var creds aws.CredentialsProvider
161161

162-
if ar.RoleARN == "" {
163-
return nil, diags.AddError(
164-
"Cannot assume IAM Role",
165-
"IAM Role ARN not set",
166-
)
167-
}
162+
total := len(c.AssumeRole)
163+
for i, ar := range c.AssumeRole {
164+
if ar.RoleARN == "" {
165+
return nil, diags.AddError(
166+
"Cannot assume IAM Role",
167+
fmt.Sprintf("IAM Role ARN not set in assume role %d of %d", i+1, total),
168+
)
169+
}
168170

169-
logger.Info(ctx, "Assuming IAM Role", map[string]any{
170-
"tf_aws.assume_role.role_arn": ar.RoleARN,
171-
"tf_aws.assume_role.session_name": ar.SessionName,
172-
"tf_aws.assume_role.external_id": ar.ExternalID,
173-
"tf_aws.assume_role.source_identity": ar.SourceIdentity,
174-
})
171+
logger.Info(ctx, "Assuming IAM Role", map[string]any{
172+
"tf_aws.assume_role.index": i,
173+
"tf_aws.assume_role.role_arn": ar.RoleARN,
174+
"tf_aws.assume_role.session_name": ar.SessionName,
175+
"tf_aws.assume_role.external_id": ar.ExternalID,
176+
"tf_aws.assume_role.source_identity": ar.SourceIdentity,
177+
})
175178

176-
// When assuming a role, we need to first authenticate the base credentials above, then assume the desired role
177-
client := stsClient(ctx, awsConfig, c)
179+
// When assuming a role, we need to first authenticate the base credentials above, then assume the desired role
180+
client := stsClient(ctx, awsConfig, c)
178181

179-
appCreds := stscreds.NewAssumeRoleProvider(client, ar.RoleARN, func(opts *stscreds.AssumeRoleOptions) {
180-
opts.RoleSessionName = ar.SessionName
181-
opts.Duration = ar.Duration
182+
appCreds := stscreds.NewAssumeRoleProvider(client, ar.RoleARN, func(opts *stscreds.AssumeRoleOptions) {
183+
opts.RoleSessionName = ar.SessionName
184+
opts.Duration = ar.Duration
182185

183-
if ar.ExternalID != "" {
184-
opts.ExternalID = aws.String(ar.ExternalID)
185-
}
186+
if ar.ExternalID != "" {
187+
opts.ExternalID = aws.String(ar.ExternalID)
188+
}
186189

187-
if ar.Policy != "" {
188-
opts.Policy = aws.String(ar.Policy)
189-
}
190+
if ar.Policy != "" {
191+
opts.Policy = aws.String(ar.Policy)
192+
}
190193

191-
if len(ar.PolicyARNs) > 0 {
192-
opts.PolicyARNs = getPolicyDescriptorTypes(ar.PolicyARNs)
193-
}
194+
if len(ar.PolicyARNs) > 0 {
195+
opts.PolicyARNs = getPolicyDescriptorTypes(ar.PolicyARNs)
196+
}
194197

195-
if len(ar.Tags) > 0 {
196-
var tags []types.Tag
197-
for k, v := range ar.Tags {
198-
tag := types.Tag{
199-
Key: aws.String(k),
200-
Value: aws.String(v),
198+
if len(ar.Tags) > 0 {
199+
var tags []types.Tag
200+
for k, v := range ar.Tags {
201+
tag := types.Tag{
202+
Key: aws.String(k),
203+
Value: aws.String(v),
204+
}
205+
tags = append(tags, tag)
201206
}
202-
tags = append(tags, tag)
203-
}
204207

205-
opts.Tags = tags
206-
}
208+
opts.Tags = tags
209+
}
207210

208-
if len(ar.TransitiveTagKeys) > 0 {
209-
opts.TransitiveTagKeys = ar.TransitiveTagKeys
210-
}
211+
if len(ar.TransitiveTagKeys) > 0 {
212+
opts.TransitiveTagKeys = ar.TransitiveTagKeys
213+
}
211214

212-
if ar.SourceIdentity != "" {
213-
opts.SourceIdentity = aws.String(ar.SourceIdentity)
215+
if ar.SourceIdentity != "" {
216+
opts.SourceIdentity = aws.String(ar.SourceIdentity)
217+
}
218+
})
219+
_, err := appCreds.Retrieve(ctx)
220+
if err != nil {
221+
return nil, diags.Append(newCannotAssumeRoleError(ar, err))
214222
}
215-
})
216-
_, err := appCreds.Retrieve(ctx)
217-
if err != nil {
218-
return nil, diags.Append(c.NewCannotAssumeRoleError(err))
223+
creds = aws.NewCredentialsCache(appCreds)
224+
awsConfig.Credentials = creds
219225
}
220-
return aws.NewCredentialsCache(appCreds), nil
226+
return creds, nil
221227
}
222228

223229
func getPolicyDescriptorTypes(policyARNs []string) []types.PolicyDescriptorType {

errors.go

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,63 @@
44
package awsbase
55

66
import (
7+
"fmt"
8+
79
"github.com/hashicorp/aws-sdk-go-base/v2/diag"
810
"github.com/hashicorp/aws-sdk-go-base/v2/internal/config"
911
)
1012

11-
// CannotAssumeRoleError occurs when AssumeRole cannot complete.
12-
type CannotAssumeRoleError = config.CannotAssumeRoleError
13+
// cannotAssumeRoleError occurs when AssumeRole cannot complete.
14+
type cannotAssumeRoleError struct {
15+
ar config.AssumeRole
16+
err error
17+
}
18+
19+
func (e cannotAssumeRoleError) Severity() diag.Severity {
20+
return diag.SeverityError
21+
}
22+
23+
func (e cannotAssumeRoleError) Summary() string {
24+
return "Cannot assume IAM Role"
25+
}
26+
27+
func (e cannotAssumeRoleError) Detail() string {
28+
return fmt.Sprintf(`IAM Role (%s) cannot be assumed.
29+
30+
There are a number of possible causes of this - the most common are:
31+
* The credentials used in order to assume the role are invalid
32+
* The credentials do not have appropriate permission to assume the role
33+
* The role ARN is not valid
34+
35+
Error: %s
36+
`, e.ar.RoleARN, e.err)
37+
}
38+
39+
func (e cannotAssumeRoleError) Equal(other diag.Diagnostic) bool {
40+
ed, ok := other.(cannotAssumeRoleError)
41+
if !ok {
42+
return false
43+
}
44+
45+
return ed.Summary() == e.Summary() && ed.Detail() == e.Detail()
46+
}
47+
48+
func (e cannotAssumeRoleError) Err() error {
49+
return e.err
50+
}
51+
52+
func newCannotAssumeRoleError(ar AssumeRole, err error) cannotAssumeRoleError {
53+
return cannotAssumeRoleError{
54+
ar: ar,
55+
err: err,
56+
}
57+
}
58+
59+
var _ diag.DiagnosticWithErr = cannotAssumeRoleError{}
1360

1461
// IsCannotAssumeRoleError returns true if the error contains the CannotAssumeRoleError type.
1562
func IsCannotAssumeRoleError(diag diag.Diagnostic) bool {
16-
_, ok := diag.(CannotAssumeRoleError)
63+
_, ok := diag.(cannotAssumeRoleError)
1764
return ok
1865
}
1966

errors_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestIsCannotAssumeRoleError(t *testing.T) {
2424
},
2525
{
2626
Name: "Top-level CannotAssumeRoleError",
27-
Diag: CannotAssumeRoleError{},
27+
Diag: cannotAssumeRoleError{},
2828
Expected: true,
2929
},
3030
}
@@ -53,7 +53,7 @@ func TestIsNoValidCredentialSourcesError(t *testing.T) {
5353
},
5454
{
5555
Name: "Top-level CannotAssumeRoleError",
56-
Diag: CannotAssumeRoleError{},
56+
Diag: cannotAssumeRoleError{},
5757
},
5858
{
5959
Name: "Top-level NoValidCredentialSourcesError",

internal/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type Config struct {
3333
AccessKey string
3434
AllowedAccountIds []string
3535
APNInfo *APNInfo
36-
AssumeRole *AssumeRole
36+
AssumeRole []AssumeRole
3737
AssumeRoleWithWebIdentity *AssumeRoleWithWebIdentity
3838
Backoff retry.BackoffDelayer
3939
CallerDocumentationURL string

internal/config/errors.go

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,58 +9,6 @@ import (
99
"github.com/hashicorp/aws-sdk-go-base/v2/diag"
1010
)
1111

12-
// CannotAssumeRoleError occurs when AssumeRole cannot complete.
13-
type CannotAssumeRoleError struct {
14-
Config *Config
15-
err error
16-
}
17-
18-
func (e CannotAssumeRoleError) Severity() diag.Severity {
19-
return diag.SeverityError
20-
}
21-
22-
func (e CannotAssumeRoleError) Summary() string {
23-
return "Cannot assume IAM Role"
24-
}
25-
26-
func (e CannotAssumeRoleError) Detail() string {
27-
if e.Config == nil || e.Config.AssumeRole == nil {
28-
return fmt.Sprintf("Error: %s", e.err)
29-
}
30-
31-
return fmt.Sprintf(`IAM Role (%s) cannot be assumed.
32-
33-
There are a number of possible causes of this - the most common are:
34-
* The credentials used in order to assume the role are invalid
35-
* The credentials do not have appropriate permission to assume the role
36-
* The role ARN is not valid
37-
38-
Error: %s
39-
`, e.Config.AssumeRole.RoleARN, e.err)
40-
}
41-
42-
func (e CannotAssumeRoleError) Equal(other diag.Diagnostic) bool {
43-
ed, ok := other.(CannotAssumeRoleError)
44-
if !ok {
45-
return false
46-
}
47-
48-
return ed.Summary() == e.Summary() && ed.Detail() == e.Detail()
49-
}
50-
51-
func (e CannotAssumeRoleError) Err() error {
52-
return e.err
53-
}
54-
55-
func (c *Config) NewCannotAssumeRoleError(err error) CannotAssumeRoleError {
56-
return CannotAssumeRoleError{
57-
Config: c,
58-
err: err,
59-
}
60-
}
61-
62-
var _ diag.DiagnosticWithErr = CannotAssumeRoleError{}
63-
6412
// CannotAssumeRoleWithWebIdentityError occurs when AssumeRoleWithWebIdentity cannot complete.
6513
type CannotAssumeRoleWithWebIdentityError struct {
6614
Config *Config

servicemocks/mock.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const (
3333

3434
MockStsAssumeRoleAccessKey = `AssumeRoleAccessKey`
3535
MockStsAssumeRoleArn = `arn:aws:iam::555555555555:role/AssumeRole`
36+
MockStsAssumeRoleArn2 = `arn:aws:iam::555555555555:role/AssumeRole2`
3637
MockStsAssumeRoleExternalId = `AssumeRoleExternalId`
3738
MockStsAssumeRoleInvalidResponseBodyInvalidClientTokenId = `<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
3839
<Error>
@@ -53,6 +54,7 @@ const (
5354
MockStsAssumeRolePolicyArn = `arn:aws:iam::555555555555:policy/AssumeRolePolicy1`
5455
MockStsAssumeRoleSecretKey = `AssumeRoleSecretKey`
5556
MockStsAssumeRoleSessionName = `AssumeRoleSessionName`
57+
MockStsAssumeRoleSessionName2 = `AssumeRoleSessionName2`
5658
MockStsAssumeRoleSessionToken = `AssumeRoleSessionToken`
5759
MockStsAssumeRoleSourceIdentity = `AssumeRoleSourceIdentity`
5860
MockStsAssumeRoleTagKey = `AssumeRoleTagKey`

0 commit comments

Comments
 (0)