我們可以使用 Terraform 在 AWS 上建立一個可以使用 OIDC 的 IAM Role,同時完成 GitHub 的設定
简而言之
Terraform不仅可以管理AWS等云服务,还可以管理GitHub资源。
在本文中,我们将使用Terraform在GitHub Actions中创建可以通过OpenID Connect进行Assume Role的IAM Role。
此外,我们将一并使用Terraform将创建的Role的arn注册到GitHub Actions的秘钥中。
请在中国方面进行参考。
-
- https://zenn.dev/kou_pg_0131/articles/gh-actions-oidc-aws
-
- https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
https://zenn.dev/miyajan/articles/github-actions-support-openid-connect
TerraformでOpenID Providerを作成する部分はこの記事を参考に作成しました
檢証所使用的程式碼可在以下儲存庫中找到。
使用中的环境
-
- terraform
v1.4.7
创建Terraform
在这里介绍了代码示例,但省略了变量的定义。
由于命名可以推测到一定程度,请将其作为传递信息的参考。
在上面的链接仓库中,包含了包含变量定义的代码,请也参考那里。
这次要制作的结构
为了使用GitHub Actions的Environments功能,我们将使用现有的GitHub存储库。
我们将创建Environments以便使用GitHub Actions的Environments功能。
我们将在其中作为secret加入IAM角色的arn,并作为变量加入地区。
我只将IAM角色附加到GitHub Actions中,而无需任何策略,只能通过Assume Role。
使用的服务提供商
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
github = {
source = "integrations/github"
version = "~> 5.0"
}
}
}
为了创建IAM角色,我们将使用AWS的Provider以及GitHub的Provider来创建GitHub资源。
由于GitHub的Provider中包含了一些过时的选项,因此请确保指定正确的Provider来使用source。
GitHub提供商的文档在这里。
https://registry.terraform.io/providers/integrations/github/latest/docs
我们已按照各个供应商的设定进行了如下操作。
provider "aws" {
region = var.aws_region
}
provider "github" {
token = var.gh_token
owner = var.gh_owner
}
使用GitHub CLI所提供的Token可以从GitHub CLI获取。
通过在执行时使用”terraform plan -var “gh_token=$(gh auth token)”进行插入。
虽然文档中表示owner是可选的,但由于没有设置owner的值导致陷入了困境,因此需要指定。
将要操作的仓库的所有者填入。
例如,对于k-kojima-yumemi/potential-palm-tree仓库,”k-kojima-yumemi”部分就是owner。
当将某功能设为模块时,需要注意的事项
在将内容转化为模块以实现再利用性时,可能会遇到Provider设置的内容并未反映的问题。
如果在模块中未指定Provider并引用或创建GitHub资源,则会使用已弃用的hashicorp/github。
在这种情况下,首次设置的Provider内容将被视为另一个Provider的信息,因此在模块内设置的项目将不会被反映。
因此,通过在定义模块资源的文件中指定以下元素,可以解决Provider错位的问题。
terraform {
required_providers {
github = {
source = "integrations/github"
version = "~> 5.0"
}
}
}
当执行terraform providers时,如果出现github的Provider已被弃用的消息,则可能发生了这种现象。
生成Environments
可以使用github_repository_environment资源来创建Environments。
resource "github_repository_environment" "gh_env" {
repository = var.gh_repo_name
environment = var.gh_repo_env_name
}
存储库必须是不包含所有者的字符串。
看起来这里的字符串和提供者端持有的所有者被连接成/{owner}/{repository}的形式来调用API。
环境应该是环境的名称,并且需要与后续指定的IAM角色匹配。
创建AWS的ID提供者
这个ID提供者是根据https://zenn.dev/miyajan/articles/github-actions-support-openid-connect进行创建的。
# Ref: https://zenn.dev/miyajan/articles/github-actions-support-openid-connect
data "http" "github_actions_openid_configuration" {
url = "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
}
data "tls_certificate" "github_actions" {
url = jsondecode(data.http.github_actions_openid_configuration.response_body).jwks_uri
}
resource "aws_iam_openid_connect_provider" "github_actions" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = data.tls_certificate.github_actions.certificates[*].sha1_fingerprint
}
output "id_provider_arn" {
value = aws_iam_openid_connect_provider.github_actions.arn
}
我将ID Provider的arn添加到Output中,以便在创建IAM Role时使用。
创建IAM角色
使用先前创建的ID提供程序进行认证,创建IAM角色。
另外,限定认证来源为创建的GitHub环境。
resource "aws_iam_role" "iam" {
name = var.role_name
assume_role_policy = jsonencode({
"Version" : "2012-10-17",
"Statement" : [
{
"Effect" : "Allow",
"Principal" : {
"Federated" : var.id_provider_arn
},
"Action" : "sts:AssumeRoleWithWebIdentity",
"Condition" : {
"StringEquals" : {
"token.actions.githubusercontent.com:aud" : "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub" : "repo:${var.repo_full_name}:environment:${var.repo_env_name}"
},
}
}
]
})
}
在token.actions.githubusercontent.com:sub上,通过指定存储库和环境来进行设置。
这里的存储库名称需要指定为{所有者}/{仓库}的格式。
在环境中注册秘密和变量
我将IAM角色的ARN和AWS区域注册到GitHub的Environment中。
在创建环境机密时,使用github_actions_environment_secret。
在变量中使用github_actions_environment_variable。
resource "github_actions_environment_secret" "gh_env_secret_arn" {
repository = var.gh_repo_name
environment = var.gh_repo_env_name
secret_name = var.gh_repo_env_secret_name
# Actually, arn is not sensitive value so just use plain text.
plaintext_value = var.aws_iam_role_arn
}
resource "github_actions_environment_variable" "gh_env_variable_region" {
repository = var.gh_repo_name
environment = var.gh_repo_env_name
variable_name = var.gh_repo_env_variable_name
value = var.aws_region
}
正如文件中所述,秘密内容本应进行加密。
在这种情况下,我们将IAM角色的ARN写入Secret,并选择了指定明文值,因为其保密性较低。
以明文值方式输入的值将以明文形式保存在terraform.tfstate文件中。
在实际处理高度保密信息时,请考虑加密或使用其他方法来输入值。
计划
我将介绍在此前内容中创建的Terraform中执行terraform plan的示例。
这是针对用于验证的GitHub仓库进行的操作。
由于环境中已存在ID提供程序,因此不会再创建ID提供程序。
此外,ID提供程序的arn已被隐藏。
module.gh_repo.data.github_repository.repo: Reading...
module.gh_repo.data.github_repository.repo: Read complete after 0s [id=potential-palm-tree]
Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.aws_iam.aws_iam_role.iam will be created
+ resource "aws_iam_role" "iam" {
+ arn = (known after apply)
+ assume_role_policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "sts:AssumeRoleWithWebIdentity"
+ Condition = {
+ StringEquals = {
+ "token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
+ "token.actions.githubusercontent.com:sub" = "repo:k-kojima-yumemi/potential-palm-tree:environment:env1"
}
}
+ Effect = "Allow"
+ Principal = {
+ Federated = "<Arn of ID Provider>"
}
},
]
+ Version = "2012-10-17"
}
)
+ create_date = (known after apply)
+ description = "Test role created in terraform"
+ force_detach_policies = false
+ id = (known after apply)
+ managed_policy_arns = (known after apply)
+ max_session_duration = 3600
+ name = "k_kojima_terraform_with_github"
+ name_prefix = (known after apply)
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
}
# module.gh_env.github_repository_environment.gh_env will be created
+ resource "github_repository_environment" "gh_env" {
+ environment = "env1"
+ id = (known after apply)
+ repository = "potential-palm-tree"
}
# module.gh_env_values.github_actions_environment_secret.gh_env_secret_arn will be created
+ resource "github_actions_environment_secret" "gh_env_secret_arn" {
+ created_at = (known after apply)
+ environment = "env1"
+ id = (known after apply)
+ plaintext_value = (sensitive value)
+ repository = "potential-palm-tree"
+ secret_name = "test_secret"
+ updated_at = (known after apply)
}
# module.gh_env_values.github_actions_environment_variable.gh_env_variable_region will be created
+ resource "github_actions_environment_variable" "gh_env_variable_region" {
+ created_at = (known after apply)
+ environment = "env1"
+ id = (known after apply)
+ repository = "potential-palm-tree"
+ updated_at = (known after apply)
+ value = "ap-northeast-1"
+ variable_name = "AWS_REGION"
}
Plan: 4 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ gh_repo_name = "k-kojima-yumemi/potential-palm-tree"
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
申请后将创建这些资源。

执行GitHub Actions
为了验证,我在下面定义了工作流程。
name: Test AWS
on:
push:
jobs:
access:
runs-on: ubuntu-latest
environment: env1
permissions:
id-token: write
steps:
- name: access
uses: aws-actions/configure-aws-credentials@v2
with:
aws-region: ${{vars.AWS_REGION}}
role-to-assume: ${{secrets.TEST_SECRET}}
这是一个在现有环境中只需要进行Assume Role操作的工作流程。可以确认它能够正常运行。
