As we know, if an application needs to access an account from within another account, it needs to assume role and gain temporary credentials (expires in 15 minutes but gets auto refreshed). This is called cross-account access. In short, app is running in application X and want to use resources from account Y.


For the sake of an example, I will also add an example where the application uses resources (IAM) from within the same account. For the cross-account access, we will focus on S3 resources.


Perquisites


Install packages below.


go get github.com/aws/aws-sdk-go-v2
go get github.com/aws/aws-sdk-go-v2/config
go get github.com/aws/aws-sdk-go-v2/service/iam
go get github.com/aws/aws-sdk-go-v2/service/s3
go get github.com/aws/aws-sdk-go-v2/service/sts

Files


aws/config.go


package aws

import (
"context"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/sts"
)

// NewDefaultConfig current credentials for the current account and returns AWS
// config. Credentials do not expire.
func NewDefaultConfig(ctx context.Context) (aws.Config, error) {
return config.LoadDefaultConfig(ctx)
}

// NewCrossAccountConfigWithRole retrieves temporary credentials from the STS
// service for assumed role and returns AWS config for cross-account access. Credentials
// last 15 minutes but get automatically refreshed.
func NewCrossAccountConfigWithRole(ctx context.Context, roleARN string) (aws.Config, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return aws.Config{}, err
}

stsClient := sts.NewFromConfig(cfg)
stsCreds := stscreds.NewAssumeRoleProvider(stsClient, roleARN)

cfg.Credentials = aws.NewCredentialsCache(stsCreds)

return cfg, nil
}

aws/iam.go


package aws

import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/iam"
)

func NewIAMClient(cfg aws.Config) *iam.Client {
return iam.NewFromConfig(cfg)
}

aws/s3.go


package aws

import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
)

func NewS3Client(cfg aws.Config) *s3.Client {
return s3.NewFromConfig(cfg)
}

api/current_account.go


package api

import (
"context"
"log"
"net/http"

"github.com/aws/aws-sdk-go-v2/service/iam"
)

// CurrentAccount holds AWS current account dependencies.
type CurrentAccount struct {
IAMClient *iam.Client
}

// IAM fetches IAM groups from the current account.
func (c CurrentAccount) IAM(w http.ResponseWriter, r *http.Request) {
out, err := c.IAMClient.ListGroups(context.Background(), nil)
if err != nil {
log.Fatal(err)
}

for _, group := range out.Groups {
log.Println(*group.GroupName)
}
}

api/cross_account.go


package api

import (
"context"
"log"
"net/http"

"github.com/aws/aws-sdk-go-v2/service/s3"
)

// CrossAccount holds AWS cross-account dependencies.
type CrossAccount struct {
S3Client *s3.Client
}

// S3 fetches S3 bucktes from a cross account.
func (c CrossAccount) S3(w http.ResponseWriter, r *http.Request) {
out, err := c.S3Client.ListBuckets(context.Background(), nil)
if err != nil {
log.Fatal(err)
}

for _, bucket := range out.Buckets {
log.Println(*bucket.Name)
}
}

main.go


package main

import (
"aws/api"
"aws/aws"
"context"
"log"
"net/http"
)

func main() {
ctx := context.Background()

currentAccountConfig, err := aws.NewDefaultConfig(ctx)
if err != nil {
log.Fatal(err)
}

crossAccountConfig, err := aws.NewCrossAccountConfigWithRole(ctx, "arn:aws:iam::1234567890:role/services")
if err != nil {
log.Fatal(err)
}

currectAccountAPI := api.CurrentAccount{
IAMClient: aws.NewIAMClient(currentAccountConfig),
}

crossAccountAPI := api.CrossAccount{
S3Client: aws.NewS3Client(crossAccountConfig),
}

httpRouter := http.NewServeMux()
httpRouter.HandleFunc("/iam", currectAccountAPI.IAM)
httpRouter.HandleFunc("/s3", crossAccountAPI.S3)

log.Fatalln(http.ListenAndServe(":1234", httpRouter))
}

AWS config


You could use normal environment variables instead if you prefer.


[default]
region = eu-west-1
output = json

[default]
aws_access_key_id = QWERTY09876541
aws_secret_access_key = QWERTY1234567890

Test


$ go run -race main.go

$ curl http://localhost:1234/iam
2023/06/03 14:53:39 admins
2023/06/03 14:53:39 devops
2023/06/03 14:53:39 developers

$ curl http://localhost:1234/s3
2023/06/03 14:53:45 inanzzz-development-cvs
2023/06/03 14:53:45 inanzzz-development-images