【问题标题】:Terraform unable to assume roles with MFA enabledTerraform 无法承担启用 MFA 的角色
【发布时间】:2019-02-25 05:07:45
【问题描述】:

让 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.

【问题讨论】:

    标签: amazon-web-services terraform amazon-iam terraform-provider-aws


    【解决方案1】:

    Terraform 目前不支持在运行时提示输入 MFA 令牌,因为它旨在以尽可能少的交互方式运行,并且显然需要对提供程序结构进行重大修改以支持这种交互式提供程序配置. this issue 有更多关于此的讨论。

    正如该问题中还提到的,最好的办法是使用某种形式的脚本/工具,该脚本/工具在运行 Terraform 之前已经承担了角色。

    我个人使用 AWS-Vault 并编写了一个小的 shim shell 脚本,我从 terraform 符号链接到该脚本(以及我想使用 AWS-Vault 获取凭据的其他内容,例如 aws),它检测到什么它被称为,使用which -a 找到“真正的”二进制文件,然后使用 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 $0)
    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 而言,它是通过环境变量获得凭据的,这很有效。

    【讨论】:

    • 你能用 shim 步骤更新吗?我无法使用 ${TARGET} var 获取 TF
    • 您是否在路径上的目标二进制文件之前使用符号链接到 shim?所以运行which terraform 应该返回指向垫片的符号链接。 which -a 应该同时显示 shim 符号链接和真正的 terraform 二进制文件。
    【解决方案2】:

    另一种方法是使用credential_process 以使用本地脚本生成凭据并将令牌缓存在新配置文件中(我们称之为tf_temp

    这个脚本会:

    • 检查令牌对于配置文件 tf_temp 是否仍然有效

    • 如果令牌有效,则使用aws configure get xxx --profile tf_temp从现有配置中提取令牌

    • 如果token无效,提示使用输入mfa token

    • 使用aws assume-role --token-code xxxx ... --profile your_profile生成会话令牌

    • 使用aws configure set xxx --profile tf_temp 设置临时配置文件令牌tf_temp

    你会:

    ~/.aws/凭据

    [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

    gist

    将此脚本移动到 /bin/mfa.sh/usr/local/bin/mfa.sh 中:

    #!/bin/sh
    set -e
    
    role=$1
    mfa_arn=$2
    profile=$3
    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 和/或 terragrunt

    【讨论】:

      【解决方案3】:

      我使用了一个非常简单但可能很脏的解决方案来解决这个问题:

      首先,让 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 来实现相同的结果。

      【讨论】:

      • 更好的是,使用AWS Vault。它做了github.com/remind101/assume-role 所做的事情,但它还允许您将您的凭据从纯文本~/.aws/credentials 文件中移出到一个加密的保险库中。
      猜你喜欢
      • 2020-04-29
      • 2018-01-15
      • 2020-07-13
      • 2018-08-06
      • 2019-09-15
      • 2022-01-04
      • 2023-02-18
      • 2017-11-20
      • 2021-10-21
      相关资源
      最近更新 更多