AWS SDK Role Assumption

aws development go

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
}