如何使用AWS Go SDK列出关联AWS账户的资源
解决AWS Organizations子账户资源列表获取问题
你当前的代码始终使用默认认证的账户身份调用Route53接口,所以只能获取当前账户的资源。AWS没有直接通过传递账户ID切换身份的方式,必须通过**IAM角色扮演(AssumeRole)**来获取子账户的临时凭证,再用该凭证创建对应服务的客户端。
另外,你的代码混合使用了AWS SDK v1和v2,这会增加复杂度,建议统一使用v2版本(你已经导入了v2的包)。
解决步骤
- 前提准备:在每个子账户中创建一个IAM角色(比如
OrganizationReadOnlyAccess),信任关系设置为允许管理账户(或运行代码的账户)扮演该角色,并且角色权限包含route53:ListHostedZones、route53domains:ListDomains等必要读取权限。 - 代码修改:遍历子账户时,调用STS服务的AssumeRole接口获取临时凭证,用该凭证创建子账户的服务客户端,再调用资源列表接口。
修改后的完整代码
package main import ( "context" "fmt" "log" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/organizations" "github.com/aws/aws-sdk-go-v2/service/route53" "github.com/aws/aws-sdk-go-v2/service/route53domains" "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/sts/types" ) // 扮演子账户角色,获取临时凭证配置 func assumeRole(ctx context.Context, accountID string, roleName string) (aws.Config, error) { // 加载默认配置(管理账户的身份) cfg, err := config.LoadDefaultConfig(ctx, config.WithDefaultRegion("us-east-1")) if err != nil { return aws.Config{}, err } stsClient := sts.NewFromConfig(cfg) resp, err := stsClient.AssumeRole(ctx, &sts.AssumeRoleInput{ RoleArn: aws.String(fmt.Sprintf("arn:aws:iam::%s:role/%s", accountID, roleName)), RoleSessionName: aws.String("org-resource-list-session"), }) if err != nil { return aws.Config{}, err } // 用临时凭证创建新的配置 return aws.Config{ Credentials: aws.NewCredentialsCache(aws.NewStaticCredentialsProvider( *resp.Credentials.AccessKeyId, *resp.Credentials.SecretAccessKey, *resp.Credentials.SessionToken, )), Region: "us-east-1", // 根据需要调整区域 }, nil } func main() { ctx := context.TODO() // 加载管理账户配置 cfg, err := config.LoadDefaultConfig(ctx, config.WithDefaultRegion("us-east-1")) if err != nil { log.Fatal(err) } // 创建Organizations客户端 orgClient := organizations.NewFromConfig(cfg) resp, err := orgClient.ListAccounts(ctx, &organizations.ListAccountsInput{}) if err != nil { log.Fatalf("获取账户列表失败: %v", err) } // 替换为你在子账户中创建的角色名称 targetRoleName := "OrganizationReadOnlyAccess" fmt.Println("账户列表:") for _, account := range resp.Accounts { accountID := *account.Id accountName := *account.Name fmt.Printf("\n=== 账户: %s (%s) ===\n", accountName, accountID) // 扮演子账户角色 subAccountCfg, err := assumeRole(ctx, accountID, targetRoleName) if err != nil { fmt.Printf("切换到账户%s失败: %v\n", accountName, err) continue } // 创建子账户的Route53Domains客户端并列出域名 r53DomainsClient := route53domains.NewFromConfig(subAccountCfg) domainsResp, err := r53DomainsClient.ListDomains(ctx, &route53domains.ListDomainsInput{}) if err != nil { fmt.Printf("获取账户%s域名失败: %v\n", accountName, err) } else { fmt.Println("域名列表:") for _, domain := range domainsResp.Domains { fmt.Println("-", *domain.DomainName) } } // 创建子账户的Route53客户端并列出托管区域 r53Client := route53.NewFromConfig(subAccountCfg) zonesResp, err := r53Client.ListHostedZones(ctx, &route53.ListHostedZonesInput{}) if err != nil { fmt.Printf("获取账户%s托管区域失败: %v\n", accountName, err) } else { fmt.Println("托管区域列表:") for _, zone := range zonesResp.HostedZones { fmt.Println("-", *zone.Name) } } } }
关键说明
- 角色扮演逻辑:
assumeRole函数负责调用STS服务获取子账户的临时凭证,生成子账户专属的配置。 - 统一SDK版本:移除了v1的session和route53包,全部使用v2的API,避免版本冲突。
- 错误处理优化:切换账户或获取资源失败时,不再直接终止程序,而是打印错误后继续处理下一个账户。
- 角色权限:确保子账户的角色拥有足够的权限来读取Route53和Route53Domains资源,必要时可以自定义权限策略。
内容的提问来源于stack exchange,提问作者Arvind Singh
相关产品推荐
相关产品推荐

