Short and sweet reminder to future self…
I previously mentioned we follow the AWS best practice of sandboxing teams or services in dedicated accounts. It’s such a common practice, most likely you do too. In turn, you use cross-account role assumption when accessing resources (ideally using aws-vault).
That’s all well and good for humans, but a common requirement is ensuring services leverage the same role assumption. The exact implementation will vary a bit depending on your language of choice, but thankfully the AWS SDK makes this easy.
Looking at the Go v2 SDK
specifically, the solution didn’t immediately jump out. The documentation is
thorough, but I myopically focussed on config
and iam
. Zooming out a bit,
the Security Token Service
(AWS STS) provides what we need. No surprise in hindsight; that’s how
ephemeral credentials are obtained for CLI-based role assumption.
Here’s an example creating a service client using role assumption:
func getClient() (*iam.Client, error) {
role := os.Getenv("ASSUME_ROLE_ARN")
if len(role) == 0 {
return nil, errors.New("failed reading ASSUME_ROLE_ARN")
}
cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
return nil, err
}
creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), role)
cfg.Credentials = aws.NewCredentialsCache(creds)
return iam.NewFromConfig(cfg), nil
}
After specifying a role, calling stscreds.NewAssumeRoleProvider
and updating
cfg.Credentials
is key. Then you can use your properly configured service
client as usual:
func getGroups() (*iam.ListGroupsOutput, error) {
svc, err := getClient()
if err != nil {
return nil, err
}
gi := iam.ListGroupsInput{}
g, err := svc.ListGroups(context.TODO(), &gi)
if err != nil {
return nil, err
}
if g.IsTruncated {
gi.Marker = g.Marker
for {
gg, err := svc.ListGroups(context.TODO(), &gi)
if err != nil {
return nil, err
}
g.Groups = append(g.Groups, gg.Groups...)
gi.Marker = gg.Marker
if !gg.IsTruncated {
break
}
}
}
return g, nil
}