Terraform 无法承担启用 MFA 的角色
Terraform unable to assume roles with MFA enabled
我很难让 Terraform 承担另一个需要 MFA 的帐户的 IAM 角色。这是我的设置
AWS 配置
[default]
region = us-west-2
output = json
[profile GEHC-000]
region = us-west-2
output = json
....
[profile GEHC-056]
source_profile = GEHC-000
role_arn = arn:aws:iam::~069:role/hc/hc-master
mfa_serial = arn:aws:iam::~183:mfa/username
external_id = ~069
AWS 凭证
[default]
aws_access_key_id = xxx
aws_secret_access_key = xxx
[GEHC-000]
aws_access_key_id = same as above
aws_secret_access_key = same as above
分配给 IAM 用户的策略
STS 政策
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRole",
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": [
"arn:aws:iam::*:role/hc/hc-master"
]
}
]
}
用户政策
{
"Statement": [
{
"Action": [
"iam:*AccessKey*",
"iam:*MFA*",
"iam:*SigningCertificate*",
"iam:UpdateLoginProfile*",
"iam:RemoveUserFromGroup*"
],
"Effect": "Allow",
"Resource": [
"arn:aws:iam::~183:mfa/${aws:username}",
"arn:aws:iam::~183:mfa/*/${aws:username}",
"arn:aws:iam::~183:mfa/*/*/${aws:username}",
"arn:aws:iam::~183:mfa/*/*/*${aws:username}",
"arn:aws:iam::~183:user/${aws:username}",
"arn:aws:iam::~183:user/*/${aws:username}",
"arn:aws:iam::~183:user/*/*/${aws:username}",
"arn:aws:iam::~183:user/*/*/*${aws:username}"
],
"Sid": "Write"
},
{
"Action": [
"iam:*Get*",
"iam:*List*"
],
"Effect": "Allow",
"Resource": [
"*"
],
"Sid": "Read"
},
{
"Action": [
"iam:CreateUser*",
"iam:UpdateUser*",
"iam:AddUserToGroup"
],
"Effect": "Allow",
"Resource": [
"*"
],
"Sid": "CreateUser"
}
],
"Version": "2012-10-17"
}
强制 MFA 政策
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BlockAnyAccessOtherThanAboveUnlessSignedInWithMFA",
"Effect": "Deny",
"NotAction": "iam:*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
main.tf
provider "aws" {
profile = "GEHC-056"
shared_credentials_file = "${pathexpand("~/.aws/config")}"
region = "${var.region}"
}
data "aws_iam_policy_document" "test" {
statement {
sid = "TestAssumeRole"
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals = {
type = "AWS"
identifiers = [
"arn:aws:iam::~183:role/hc-devops",
]
}
sid = "BuUserTrustDocument"
effect = "Allow"
principals = {
type = "Federated"
identifiers = [
"arn:aws:iam::~875:saml-provider/ge-saml-for-aws",
]
}
condition {
test = "StringEquals"
variable = "SAML:aud"
values = ["https://signin.aws.amazon.com/saml"]
}
}
}
resource "aws_iam_role" "test_role" {
name = "test_role"
path = "/"
assume_role_policy = "${data.aws_iam_policy_document.test.json}"
}
获取来电者身份
bash-4.4$ aws --profile GEHC-056 sts get-caller-identity
Enter MFA code for arn:aws:iam::772660252183:mfa/503072343:
{
"UserId": "AROAIWCCLC2BGRPQMJC7U:botocore-session-1537474244",
"Account": "730993910069",
"Arn": "arn:aws:sts::730993910069:assumed-role/hc-master/botocore-session-1537474244"
}
错误:
bash-4.4$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
Error: Error refreshing state: 1 error(s) occurred:
* provider.aws: Error creating AWS session: AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.
Terraform 当前不支持在 运行 时提示输入 MFA 令牌,因为它旨在以尽可能少的交互方式 运行 并且显然需要大量返工支持此交互式提供程序配置的提供程序结构。 this issue.
中对此有更多讨论
正如那个问题中提到的那样,最好的选择是使用某种形式的 script/tool,它在 运行ning Terraform 之前已经承担了这个角色。
我个人使用 AWS-Vault 并编写了一个小的 shim shell 脚本,我从 terraform
符号链接到它(以及我想要的其他东西,例如 aws
使用 AWS-Vault 获取凭证)检测它被称为什么,使用 which -a
找到 "real" 二进制文件,然后使用 AWS-Vault 的 exec 运行 目标命令指定凭据。
我的脚本如下所示:
#!/bin/bash
set -eo pipefail
# Provides a shim to override target executables so that it is executed through aws-vault
# See https://github.com/99designs/aws-vault/blob/ae56f73f630601fc36f0d68c9df19ac53e987369/USAGE.md#overriding-the-aws-cli-to-use-aws-vault for more information about using it for the AWS CLI.
# Work out what we're shimming and then find the non shim version so we can execute that.
# which -a returns a sorted list of the order of binaries that are on the PATH so we want the second one.
INVOKED=$(basename [=10=])
TARGET=$(which -a ${INVOKED} | tail -n +2 | head -n 1)
if [ -z ${AWS_VAULT} ]; then
AWS_PROFILE="${AWS_DEFAULT_PROFILE:-read-only}"
(>&2 echo "Using temporary credentials from ${AWS_PROFILE} profile...")
exec aws-vault exec "${AWS_PROFILE}" --assume-role-ttl=60m -- "${TARGET}" "$@"
else
# If AWS_VAULT is already set then we want to just use the existing session instead of nesting them
exec "${TARGET}" "$@"
fi
它将使用您的 ~/.aws/config
文件中与您设置的 AWS_DEFAULT_PROFILE
环境变量匹配的配置文件,默认为 read-only
配置文件,这可能是也可能不是有用的默认值为你。这确保 AWS-Vault 承担 IAM 角色,获取凭证并将它们设置为目标进程的环境变量。
这意味着就 Terraform 而言,它是通过环境变量获得凭据的,这很有效。
我使用了一个非常简单但可能很脏的解决方案来解决这个问题:
首先,让 TF 从环境变量中选择凭据。那么:
AWS 凭证文件:
[access]
aws_access_key_id = ...
aws_secret_access_key = ...
region = ap-southeast-2
output = json
[target]
role_arn = arn:aws:iam::<target nnn>:role/admin
source_profile = access
mfa_serial = arn:aws:iam::<access nnn>:mfa/my-user
在控制台中
CREDENTIAL=$(aws --profile target sts assume-role \
--role-arn arn:aws:iam::<target nnn>:role/admin --role-session-name TFsession \
--output text \
--query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken,Expiration]")
<enter MFA>
#echo "CREDENTIAL: ${CREDENTIAL}"
export AWS_ACCESS_KEY_ID=$(echo ${CREDENTIAL} | cut -d ' ' -f 1)
export AWS_SECRET_ACCESS_KEY=$(echo ${CREDENTIAL} | cut -d ' ' -f 2)
export AWS_SESSION_TOKEN=$(echo ${CREDENTIAL} | cut -d ' ' -f 3)
terraform plan
更新:更好的解决方案是使用 https://github.com/remind101/assume-role 来获得相同的结果。
另一种方法是使用 credential_process
以使用本地脚本生成凭据并将令牌缓存在新配置文件中(我们称之为 tf_temp
)
此脚本将:
检查令牌对个人资料是否仍然有效tf_temp
如果令牌有效,使用aws configure get xxx --profile tf_temp
从现有配置中提取令牌
如果令牌无效,提示使用输入mfa令牌
使用 aws assume-role --token-code xxxx ... --profile your_profile
生成会话令牌
使用 aws configure set xxx --profile tf_temp
设置临时配置文件令牌 tf_temp
你会:
~/.aws/credentials
[prod]
aws_secret_access_key = redacted
aws_access_key_id = redacted
[tf_temp]
[tf]
credential_process = sh -c 'mfa.sh arn:aws:iam::{account_id}:role/{role} arn:aws:iam::{account_id}:mfa/{mfa_entry} prod 2> $(tty)'
mfa.sh
将此脚本移至 /bin/mfa.sh
或 /usr/local/bin/mfa.sh
:
#!/bin/sh
set -e
role=
mfa_arn=
profile=
temp_profile=tf_temp
if [ -z $role ]; then echo "no role specified"; exit 1; fi
if [ -z $mfa_arn ]; then echo "no mfa arn specified"; exit 1; fi
if [ -z $profile ]; then echo "no profile specified"; exit 1; fi
resp=$(aws sts get-caller-identity --profile $temp_profile | jq '.UserId')
if [ ! -z $resp ]; then
echo '{
"Version": 1,
"AccessKeyId": "'"$(aws configure get aws_access_key_id --profile $temp_profile)"'",
"SecretAccessKey": "'"$(aws configure get aws_secret_access_key --profile $temp_profile)"'",
"SessionToken": "'"$(aws configure get aws_session_token --profile $temp_profile)"'",
"Expiration": "'"$(aws configure get expiration --profile $temp_profile)"'"
}'
exit 0
fi
read -p "Enter MFA token: " mfa_token
if [ -z $mfa_token ]; then echo "MFA token can't be empty"; exit 1; fi
data=$(aws sts assume-role --role-arn $role \
--profile $profile \
--role-session-name "$(tr -dc A-Za-z0-9 </dev/urandom | head -c 20)" \
--serial-number $mfa_arn \
--token-code $mfa_token | jq '.Credentials')
aws_access_key_id=$(echo $data | jq -r '.AccessKeyId')
aws_secret_access_key=$(echo $data | jq -r '.SecretAccessKey')
aws_session_token=$(echo $data | jq -r '.SessionToken')
expiration=$(echo $data | jq -r '.Expiration')
aws configure set aws_access_key_id $aws_access_key_id --profile $temp_profile
aws configure set aws_secret_access_key $aws_secret_access_key --profile $temp_profile
aws configure set aws_session_token $aws_session_token --profile $temp_profile
aws configure set expiration $expiration --profile $temp_profile
echo '{
"Version": 1,
"AccessKeyId": "'"$aws_access_key_id"'",
"SecretAccessKey": "'"$aws_secret_access_key"'",
"SessionToken": "'"$aws_session_token"'",
"Expiration": "'"$expiration"'"
}'
在提供商设置中使用 tf
配置文件。第一次会提示mfa token :
# terraform apply
Enter MFA token: 428313
此解决方案适用于 terraform and/or terragrunt
我很难让 Terraform 承担另一个需要 MFA 的帐户的 IAM 角色。这是我的设置
AWS 配置
[default]
region = us-west-2
output = json
[profile GEHC-000]
region = us-west-2
output = json
....
[profile GEHC-056]
source_profile = GEHC-000
role_arn = arn:aws:iam::~069:role/hc/hc-master
mfa_serial = arn:aws:iam::~183:mfa/username
external_id = ~069
AWS 凭证
[default]
aws_access_key_id = xxx
aws_secret_access_key = xxx
[GEHC-000]
aws_access_key_id = same as above
aws_secret_access_key = same as above
分配给 IAM 用户的策略
STS 政策
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AssumeRole",
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Resource": [
"arn:aws:iam::*:role/hc/hc-master"
]
}
]
}
用户政策
{
"Statement": [
{
"Action": [
"iam:*AccessKey*",
"iam:*MFA*",
"iam:*SigningCertificate*",
"iam:UpdateLoginProfile*",
"iam:RemoveUserFromGroup*"
],
"Effect": "Allow",
"Resource": [
"arn:aws:iam::~183:mfa/${aws:username}",
"arn:aws:iam::~183:mfa/*/${aws:username}",
"arn:aws:iam::~183:mfa/*/*/${aws:username}",
"arn:aws:iam::~183:mfa/*/*/*${aws:username}",
"arn:aws:iam::~183:user/${aws:username}",
"arn:aws:iam::~183:user/*/${aws:username}",
"arn:aws:iam::~183:user/*/*/${aws:username}",
"arn:aws:iam::~183:user/*/*/*${aws:username}"
],
"Sid": "Write"
},
{
"Action": [
"iam:*Get*",
"iam:*List*"
],
"Effect": "Allow",
"Resource": [
"*"
],
"Sid": "Read"
},
{
"Action": [
"iam:CreateUser*",
"iam:UpdateUser*",
"iam:AddUserToGroup"
],
"Effect": "Allow",
"Resource": [
"*"
],
"Sid": "CreateUser"
}
],
"Version": "2012-10-17"
}
强制 MFA 政策
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "BlockAnyAccessOtherThanAboveUnlessSignedInWithMFA",
"Effect": "Deny",
"NotAction": "iam:*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
main.tf
provider "aws" {
profile = "GEHC-056"
shared_credentials_file = "${pathexpand("~/.aws/config")}"
region = "${var.region}"
}
data "aws_iam_policy_document" "test" {
statement {
sid = "TestAssumeRole"
effect = "Allow"
actions = [
"sts:AssumeRole",
]
principals = {
type = "AWS"
identifiers = [
"arn:aws:iam::~183:role/hc-devops",
]
}
sid = "BuUserTrustDocument"
effect = "Allow"
principals = {
type = "Federated"
identifiers = [
"arn:aws:iam::~875:saml-provider/ge-saml-for-aws",
]
}
condition {
test = "StringEquals"
variable = "SAML:aud"
values = ["https://signin.aws.amazon.com/saml"]
}
}
}
resource "aws_iam_role" "test_role" {
name = "test_role"
path = "/"
assume_role_policy = "${data.aws_iam_policy_document.test.json}"
}
获取来电者身份
bash-4.4$ aws --profile GEHC-056 sts get-caller-identity
Enter MFA code for arn:aws:iam::772660252183:mfa/503072343:
{
"UserId": "AROAIWCCLC2BGRPQMJC7U:botocore-session-1537474244",
"Account": "730993910069",
"Arn": "arn:aws:sts::730993910069:assumed-role/hc-master/botocore-session-1537474244"
}
错误:
bash-4.4$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
Error: Error refreshing state: 1 error(s) occurred:
* provider.aws: Error creating AWS session: AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.
Terraform 当前不支持在 运行 时提示输入 MFA 令牌,因为它旨在以尽可能少的交互方式 运行 并且显然需要大量返工支持此交互式提供程序配置的提供程序结构。 this issue.
中对此有更多讨论正如那个问题中提到的那样,最好的选择是使用某种形式的 script/tool,它在 运行ning Terraform 之前已经承担了这个角色。
我个人使用 AWS-Vault 并编写了一个小的 shim shell 脚本,我从 terraform
符号链接到它(以及我想要的其他东西,例如 aws
使用 AWS-Vault 获取凭证)检测它被称为什么,使用 which -a
找到 "real" 二进制文件,然后使用 AWS-Vault 的 exec 运行 目标命令指定凭据。
我的脚本如下所示:
#!/bin/bash
set -eo pipefail
# Provides a shim to override target executables so that it is executed through aws-vault
# See https://github.com/99designs/aws-vault/blob/ae56f73f630601fc36f0d68c9df19ac53e987369/USAGE.md#overriding-the-aws-cli-to-use-aws-vault for more information about using it for the AWS CLI.
# Work out what we're shimming and then find the non shim version so we can execute that.
# which -a returns a sorted list of the order of binaries that are on the PATH so we want the second one.
INVOKED=$(basename [=10=])
TARGET=$(which -a ${INVOKED} | tail -n +2 | head -n 1)
if [ -z ${AWS_VAULT} ]; then
AWS_PROFILE="${AWS_DEFAULT_PROFILE:-read-only}"
(>&2 echo "Using temporary credentials from ${AWS_PROFILE} profile...")
exec aws-vault exec "${AWS_PROFILE}" --assume-role-ttl=60m -- "${TARGET}" "$@"
else
# If AWS_VAULT is already set then we want to just use the existing session instead of nesting them
exec "${TARGET}" "$@"
fi
它将使用您的 ~/.aws/config
文件中与您设置的 AWS_DEFAULT_PROFILE
环境变量匹配的配置文件,默认为 read-only
配置文件,这可能是也可能不是有用的默认值为你。这确保 AWS-Vault 承担 IAM 角色,获取凭证并将它们设置为目标进程的环境变量。
这意味着就 Terraform 而言,它是通过环境变量获得凭据的,这很有效。
我使用了一个非常简单但可能很脏的解决方案来解决这个问题:
首先,让 TF 从环境变量中选择凭据。那么:
AWS 凭证文件:
[access]
aws_access_key_id = ...
aws_secret_access_key = ...
region = ap-southeast-2
output = json
[target]
role_arn = arn:aws:iam::<target nnn>:role/admin
source_profile = access
mfa_serial = arn:aws:iam::<access nnn>:mfa/my-user
在控制台中
CREDENTIAL=$(aws --profile target sts assume-role \
--role-arn arn:aws:iam::<target nnn>:role/admin --role-session-name TFsession \
--output text \
--query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken,Expiration]")
<enter MFA>
#echo "CREDENTIAL: ${CREDENTIAL}"
export AWS_ACCESS_KEY_ID=$(echo ${CREDENTIAL} | cut -d ' ' -f 1)
export AWS_SECRET_ACCESS_KEY=$(echo ${CREDENTIAL} | cut -d ' ' -f 2)
export AWS_SESSION_TOKEN=$(echo ${CREDENTIAL} | cut -d ' ' -f 3)
terraform plan
更新:更好的解决方案是使用 https://github.com/remind101/assume-role 来获得相同的结果。
另一种方法是使用 credential_process
以使用本地脚本生成凭据并将令牌缓存在新配置文件中(我们称之为 tf_temp
)
此脚本将:
检查令牌对个人资料是否仍然有效
tf_temp
如果令牌有效,使用
从现有配置中提取令牌aws configure get xxx --profile tf_temp
如果令牌无效,提示使用输入mfa令牌
使用
生成会话令牌aws assume-role --token-code xxxx ... --profile your_profile
使用
设置临时配置文件令牌aws configure set xxx --profile tf_temp
tf_temp
你会:
~/.aws/credentials
[prod]
aws_secret_access_key = redacted
aws_access_key_id = redacted
[tf_temp]
[tf]
credential_process = sh -c 'mfa.sh arn:aws:iam::{account_id}:role/{role} arn:aws:iam::{account_id}:mfa/{mfa_entry} prod 2> $(tty)'
mfa.sh
将此脚本移至 /bin/mfa.sh
或 /usr/local/bin/mfa.sh
:
#!/bin/sh
set -e
role=
mfa_arn=
profile=
temp_profile=tf_temp
if [ -z $role ]; then echo "no role specified"; exit 1; fi
if [ -z $mfa_arn ]; then echo "no mfa arn specified"; exit 1; fi
if [ -z $profile ]; then echo "no profile specified"; exit 1; fi
resp=$(aws sts get-caller-identity --profile $temp_profile | jq '.UserId')
if [ ! -z $resp ]; then
echo '{
"Version": 1,
"AccessKeyId": "'"$(aws configure get aws_access_key_id --profile $temp_profile)"'",
"SecretAccessKey": "'"$(aws configure get aws_secret_access_key --profile $temp_profile)"'",
"SessionToken": "'"$(aws configure get aws_session_token --profile $temp_profile)"'",
"Expiration": "'"$(aws configure get expiration --profile $temp_profile)"'"
}'
exit 0
fi
read -p "Enter MFA token: " mfa_token
if [ -z $mfa_token ]; then echo "MFA token can't be empty"; exit 1; fi
data=$(aws sts assume-role --role-arn $role \
--profile $profile \
--role-session-name "$(tr -dc A-Za-z0-9 </dev/urandom | head -c 20)" \
--serial-number $mfa_arn \
--token-code $mfa_token | jq '.Credentials')
aws_access_key_id=$(echo $data | jq -r '.AccessKeyId')
aws_secret_access_key=$(echo $data | jq -r '.SecretAccessKey')
aws_session_token=$(echo $data | jq -r '.SessionToken')
expiration=$(echo $data | jq -r '.Expiration')
aws configure set aws_access_key_id $aws_access_key_id --profile $temp_profile
aws configure set aws_secret_access_key $aws_secret_access_key --profile $temp_profile
aws configure set aws_session_token $aws_session_token --profile $temp_profile
aws configure set expiration $expiration --profile $temp_profile
echo '{
"Version": 1,
"AccessKeyId": "'"$aws_access_key_id"'",
"SecretAccessKey": "'"$aws_secret_access_key"'",
"SessionToken": "'"$aws_session_token"'",
"Expiration": "'"$expiration"'"
}'
在提供商设置中使用 tf
配置文件。第一次会提示mfa token :
# terraform apply
Enter MFA token: 428313
此解决方案适用于 terraform and/or terragrunt