AWS 访问 Google Cloud 的 Workload Identity Federation, 中文文档称为工作负载身份联合. 可以在 aws 的机器上通过 aws 的身份认证并访问 Google Cloud 服务. 相关文档:
- 介绍: Workload Identity Federation
- 配置: Configure Workload Identity Federation with AWS or Azure
- 配置: Configure Workload Identity Federation with other identity providers
- 支持与限制: Identity federation: products and limitations
- IAM 介绍: IAM overview
这里展示在网页端控制台 Console 配置 Provider 类型为 AWS 的 Workload Identity Federation 不通过服务账号直接访问资源的流程.
准备工作
-
(可选) Google Cloud 创建专用项目用于管理工作负载身份池.
-
需要确保项目开启了 Cloud Billing (文档标注, 我这边直接打开了).
-
创建 AWS Role 并绑定给需要访问 Google Cloud 的 EC2.
找到 AWS IAM / Roles, 点击 Create Role 创建身份, 类型我选择的 AWS Service, Use case 选的 EC2.
起名字, 下文中用
{AWS_ROLE}
代替. 找到 EC2, 页面右上角 Actions / Security / Modify IAM role 将身份赋给它.
配置 Google Cloud
创建工作负载身份池 & 添加身份提供方
-
Console 在 IAM & Admin 找到 Workload Identity Federation, 选择 create pool 创建一个工作负载身份池.
起名字, 如果点出了 Pool ID, 和 Pool Name 保持一致就行, 下文中用
{POOL_ID}
代替. continue 下一步. -
添加 Provider, 类型选择 AWS, 起名字, 下文中用
{PROVIDER_NAME}
代替, 输入 AWS account id, 应为12位数字, 下文中用{AWS_ACCOUNT}
代替. continue 下一步.目前 provider 类型有 AWS, OIDC, SAML, 后两种方法更加通用, 可以配置任意支持 OIDC/SAML 的 IdP.
-
配置 Provider Attributes
工作负载身份池使用 ABAC 的方式授权, 对于 AWS Provider 会调用 GetCallerIdentity 接口, 可以从返回的内容中提取 attribute.
这里解释下 attribute, 中文文档称为特性, 实际上就是资源的属性, 比如这里取 assertion.arn 映射为 google.subject, 后面配置访问权限的时候就可以说 subject 为给定 arn 的 workload identity, 以此来授权.
这里可以看到有两条预先填好的映射:
1google.subject=assertion.arn 2attribute.aws_role=assertion.arn.contains('assumed-role') ? assertion.arn.extract('{account_arn}assumed-role/') + 'assumed-role/' + assertion.arn.extract('assumed-role/{role_name}/') : assertion.arn
第一条 google.subject 是必需的, 两条都是取 arn 作为内容, 后者额外支持 assumed role.
attribute.aws_role
这样以attribute
开头的是自定义特性, 名字可以随便写, 这里就不改了. 可以添加如下映射方便针对 aws 账号级别的权限管理:1attribute.aws_account=assertion.account
-
(可选) 定义 Attribute Conditions, 如果条件计算结果不为
true
, 则会拒绝凭据. 这个也是 CEL 表达式, 这里就先不填了. -
save 完成创建.
授予工作负载身份池直接资源访问权限
-
Console 在 IAM & Admin 找到 IAM, 点击 grant access 添加权限.
-
指定主账号 Principal, 也就是指定对应的 attribute, 根据之前设置的 attribute mapping, 这里可以是:
-
按 arn, 也就是 google.subject 指定
1principal://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{POOL_ID}/subject/{ARN}
这里的
{PROJECT_NUMBER}
是数字格式的那个, 在首页 Project Info 可以找到这样可以把权限授予指定的 ec2. 关于
{ARN}
的值, 这里我的用法是 assumed role, 所以是arn:aws:sts::{AWS_ACCOUNT}:assumed-role/{AWS_ROLE}/{EC2_INSTANCE_ID}
, 具体可以调用 GetCallerIdentity 查看结果. -
按 aws_role 指定
1principalSet://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{POOL_ID}/attribute.aws_role/arn:aws:sts::{AWS_ACCOUNT}:assumed-role/{AWS_ROLE}
最后这段 arn 是根据前面 mapping 里设置的规则提取出来的. 注意前缀是 principalSet.
-
按 aws_account 指定, 其实这个和上面那条一样, 都是自定义属性
1principalSet://iam.googleapis.com/projects/{PROJECT_NUMBER}/locations/global/workloadIdentityPools/{POOL_ID}/attribute.aws_account/{AWS_ACCOUNT}
-
直接资源访问的情况下, 不需要添加 OAuth 相关权限, 直接添加对应资源的访问权限即可.
这里用 Cloud Translation 接口为例, 翻译接口需要 "cloudtranslate.generalModels.predict" 等权限
把权限输入进去, 可以看到拥有该权限的 Roles, 这里选择 Cloud Translation API Editor 添加即可.
使用凭据配置访问 Google Cloud
-
Console 在 IAM & Admin 找到 Workload Identity Federation, 点选我们刚创建的 identity pool
在右侧面板的 connected service accounts 标签下选择 download config.
会得到一个
clientLibraryConfig-{PROVIDER_NAME}.json
文件, 上传到要访问 Google Cloud 的 EC2 机器上. -
配置环境变量
1export GOOGLE_APPLICATION_CREDENTIALS=/path/to/clientLibraryConfig-{PROVIDER_NAME}.json
配置文件路径, 记得替换.
-
配置完成, 支持读取该配置的库会自动处理访问凭据的获取, 这里以 go 调用 Cloud Translation V3 为例, 编译运行以下代码:
1package main 2 3import ( 4 "context" 5 "fmt" 6 7 translate "cloud.google.com/go/translate/apiv3" 8 "cloud.google.com/go/translate/apiv3/translatepb" 9) 10 11func translateText(projectID string, sourceLang string, targetLang string, text string) (string, error) { 12 // 初始化 client 13 ctx := context.Background() 14 client, err := translate.NewTranslationClient(ctx) 15 if err != nil { 16 return "", fmt.Errorf("NewTranslationClient: %w", err) 17 } 18 defer client.Close() 19 20 // 调用请求 21 req := &translatepb.TranslateTextRequest{ 22 Parent: fmt.Sprintf("projects/%s/locations/global", projectID), 23 SourceLanguageCode: sourceLang, 24 TargetLanguageCode: targetLang, 25 MimeType: "text/plain", 26 Contents: []string{text}, 27 } 28 29 resp, err := client.TranslateText(ctx, req) 30 if err != nil { 31 return "", fmt.Errorf("TranslateText: %w", err) 32 } 33 34 results := resp.GetTranslations() 35 if len(results) != 1 { 36 fmt.Printf("warning: len(results) = %d\n", len(results)) 37 } 38 39 if len(results) == 0 { 40 return "", fmt.Errorf("No result") 41 } 42 return results[0].GetTranslatedText(), nil 43} 44 45func main() { 46 // 这里的 PROJECT_ID 是字符串id, 记得替换 47 resp, err := translateText("{PROJECT_ID}", "zh", "en", "那你能帮帮我吗") 48 if err != nil { 49 panic(err) 50 } 51 fmt.Printf("answer: %s\n", resp) 52}
输出:
answer: So can you help me?