首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >具有AWS和Terraform的角色切换(承担角色)

具有AWS和Terraform的角色切换(承担角色)
EN

Stack Overflow用户
提问于 2017-09-25 09:58:51
回答 1查看 2.3K关注 0票数 3

背景:

我们已经开始研究如何使用Terraform生成基础设施,而不是直接使用Cloudformation。

我们有多个AWS帐户,分别用于Live、QA和Dev环境(由于堆栈的复杂性和潜在的客户端服务灾难性破坏,完全分离了关注点)。我们的帐户已经接通了。

使用Cloudformation,我们进行角色切换,对一个主要AWS帐户进行身份验证,然后使用假定的角色在正确的帐户中建立堆栈。

问题的关键:

这有可能吗?(请不要有大量肮脏的黑客!)在Terraform?我们一直在尝试此过程,但在尝试运行Terraform或Build时遇到了以下错误

" The role ' arn:aws:iam::ACCOUNTID:role/ASSUMEDROLE" cannot be assumed.'

我们的提供商交换代码是:

代码语言:javascript
复制
# Configure the AWS Provider
provider "aws" {
  region = "${var.aws_region}"
  profile = "${var.profile}"
  assume_role {
    role_arn = "arn:aws:iam::${lookup(var.aws_account_id, var.tag_environment)}:role/MYASSUMEROLE"
  }
}

从几个小时的谷歌搜索,阅读博客文章和Terraform开放的bug列表,这似乎是什么东西还不支持?

我们已经看到,至少有一个人正在创建shell脚本来尝试进行身份验证,然后再通过。这似乎是一个非常丑陋的黑客,使其发挥作用。

,有人真的让MFA启动了账户吗?

我们在HashiCorp的团队在Cons和研讨会上交谈时得到了非常模糊的回应。

EN

回答 1

Stack Overflow用户

发布于 2020-10-09 09:08:19

我管理一个拥有100多个帐户的AWS组织。在我们称为identity的帐户中,每个人都有一个IAM用户。然后,他们sts:AssumeRole到IAM在其他帐户中的角色具有信任关系,并将identity帐户命名为可信帐户。用户负责运行我提供的脚本来生成MFA aws配置配置文件。地形本身并不是这样做的,因为需要输入手动代码。

设置角色的技巧

使IAM组具有身份,并允许他们在所需的帐户中承担相应的角色。确保还为用户提供权限,使其能够自我管理身份帐户中的密码和MFA设置。确保在自管理权限上没有MFA条件,因为如果它们由于条件而没有权限,则无法添加MFA设备。这是鸡和蛋的问题。在建立了MFA之后,人们将需要登录并返回MFA,以满足MFA关于IAM政策的条件。

在其他帐户中创建角色时,必须创建信任策略来信任identity帐户。当您这样做时,我建议将以下条件添加到true:MultiFactorAuthPresent

设置aws配置和凭据文件

我的建议是创建一个必须在您的组织中设置的配置文件名称模式。你可以在你的配置中有很多很多的配置文件。我有上百个。它们是生成的,而不是手动维护的。

代码语言:javascript
复制
[org]
aws_access_key_id     = SomeKey
aws_secret_access_key = SomeSecretKey

aws configure set profile.org.username gmiller.cli

代码语言:javascript
复制
[profile org]
region   = us-west-2
username = jsmith
roles    = admin,read,terraform
accounts          = identity,shared_services,dev_a,dev_b,dev_c,uat_a,uat_b,uat_c
account_numbers   = 
  identity        = 566179001270272
  shared_services = 886917640172339
  dev_a           = 505685932297420
  dev_b           = 488489750836019
  dev_c           = 695182558652006
  uat_a           = 123189319014809
  uat_b           = 705170270846976
  uat_c           = 608206892249907

通过脚本生成mfa配置文件

我的脚本使用您的非MFA AccessKey和SecretAccessKey来请求MFA支持的auth键。为此,您可以调用aws中的mfa命令,并传递当前的MFA代码。然后,我的脚本解析返回体并创建一个新的配置文件,将_mfa添加到原始配置文件名的末尾。因此,每当您想要使用profile foo但它需要MFA时,只需指定profile foo_mfa即可。如果收到消息说密钥过期,则需要再次运行脚本。

关于这个脚本的一个注释,我已经把它重新加工成了一个更好的版本。但是它和一些我还不想分享的东西混在一起,也许有一天我会在清理的时候把它放出来。这是我用bash写的第一个版本。一切都很好。它还在您指定的配置文件中旋转您的键。它生成一个新的键,更新您的配置文件以使用新的键。然后它会删除你的旧钥匙。每次执行死刑都会这样做。因此,这个脚本还会旋转您的键,这样您就不必因为组织策略而记住或被锁定。

脚本还为您生成所有其他策略。您可以列出您想要配置文件的所有帐户和角色组合。然后,您必须在地图account_numbers中输入帐号

不要忘记,您可以使用像configure get profile.cde.account_numbers.identity 566179001270272这样的命令来设置配置。我还喜欢在~/.aws目录中加上其他的AWS配置。

运行:~/.aws/mfa.sh --realm org --code 729376

从您的源配置文件org中,这将生成以下内容:

代码语言:javascript
复制
[org_mfa]
aws_access_key_id     = KeyThatWillExpire
aws_secret_access_key = SecretKeyThatWillExpire
aws_session_token     = SessionTokenThatWillExpire/////////////gornucibawowovvawumekuvekorsekotworwatandencitezesodupusowoimmelavdufzocpunbofubafdofizagvuchecufihencehfejjehdaakacmudkiutmotuwwomcoejbokazejudocetbovmifwavawvilidmalwermizmurtutotabujobgajpihsoticoowitoicubukbuglahicpatjuswodiklawciredemkukudapafietwepophibtetdildewdivwizhadunantizozatohojasejorjeivirurenmajrudsopujkalahoidugacsogogojwaprildibovgabzirajimwegegupnidukogafupaniwutudtiruntuzsogucopawafuvudfimozasbitokpulduhwagjubbevamatuopijogihaj

您可以检查它是否可以使用像:aws --profile=org_mfa sts get-caller-identity这样的命令

然后,您可以使所有其他配置文件都期望org_mfa存在。这对于运行cli命令很有用,但对于terraform,请看下面的内容。我的脚本生成的概要文件将自动为您完成此操作。

代码语言:javascript
复制
[profile org_some_account_terraform]
source_profile = org_mfa
role_arn       = arn:aws:iam::123otheraccount321:role/terraform
region         = us-west-2
output         = json

在Terraform中,可以为profileassume_role属性使用变量。在您的组织中,有一个标准的角色命名模式是有回报的。不要让用户传递他们想要使用的配置文件,在terraform代码中命令用户创建符合代码期望的配置文件。我没有收到关于这个的抱怨。这让生活变得非常简单。

具有指定MFA角色的Terraform提供程序:

代码语言:javascript
复制
provider "aws" {
  version = "~> 2.38.0"
  alias   = "shared_services"
  profile = format("%s_mfa", var.realm)
  region  = var.region

  assume_role {
    role_arn = "arn:aws:iam::${var.shared_services_account_number}:role/terraform"
  }
}

此提供程序在我称为shared_services的帐户中为aws资源创建建立会话。它使用mfa脚本通过具有用户访问密钥和秘密访问密钥的组织概要文件生成的配置文件进行操作。

然后,如果需要的话,利用提供者映射将特定的提供程序传递给特定的模块。请参见下面的providers映射:

代码语言:javascript
复制
module "bootstrap" {
  source = "../_modules/bootstrap/global"

  providers = {
    aws                 = aws
    aws.org_identity    = aws.org_identity
    aws.shared_services = aws.shared_services
  }

  iam_alias = var.iam_alias
  realm     = var.realm
}

我已经运行这个设置至少两年了。它在没有失望和问题的情况下奏效。我希望这能回答你的问题。我的剧本如下:

代码语言:javascript
复制
#!/usr/bin/env bash

# TODO generate config and credentials from gomplate
# TODO test each role assumption to validate config vs reality

# https://natelandau.com/boilerplate-shell-script-template/
# ##################################################
# My Generic BASH script template
#
version="1.0.0"               # Sets version variable
#
scriptTemplateVersion="1.3.0" # Version of scriptTemplate.sh that this script is based on
#                               v.1.1.0 - Added 'debug' option
#                               v.1.1.1 - Moved all shared variables to Utils
#                                       - Added $PASS variable when -p is passed
#                               v.1.2.0 - Added 'checkDependencies' function to ensure needed
#                                         Bash packages are installed prior to execution
#                               v.1.3.0 - Can now pass CLI without an option to $args
#
# HISTORY:
#
# * DATE - v1.0.0  - First Creation
#
# ##################################################

# Provide a variable with the location of this script.
scriptPath="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
scriptParentPath="${scriptPath%/*}"

# Source Scripting Utilities
# -----------------------------------
# These shared utilities provide many functions which are needed to provide
# the functionality in this boilerplate. This script will fail if they can
# not be found.
# -----------------------------------

# utilsLocation="${scriptParentPath}/lib/utils.sh" # Update this path to find the utilities.

# if [ -f "${utilsLocation}" ]; then
#   source "${utilsLocation}"
# else
#   echo "Please find the file util.sh and add a reference to it in this script. Exiting."
#   exit 1
# fi

# trapCleanup Function
# -----------------------------------
# Any actions that should be taken if the script is prematurely
# exited.  Always call this function at the top of your script.
# -----------------------------------
# function trapCleanup() {
#   echo ""
#   if is_dir "${tmpDir}"; then
#     rm -r "${tmpDir}"
#   fi
#   die "Exit trapped."  # Edit this if you like.
# }

# Set Flags
# -----------------------------------
# Flags which can be overridden by user input.
# Default values are below
# -----------------------------------
quiet=0
printLog=0
verbose=0
force=0
strict=0
debug=0
args=()

# args
code=""
realm=""
region="us-west-2"
mfa_arn=""
username=""
account_number=""
skip_key_rotate=0
skip_realm_config=0
duration_seconds=129600

# scratch vars
exit_do_to_missing_required_vars=0
return_body=""
aws_session_token=""
secret_access_key=""
access_key_id=""
old_key_id=""
new_key_id=""
old_secret=""
new_secret=""
declare -a accounts
declare -a roles

# Set Temp Directory
# -----------------------------------
# Create temp directory with three random numbers and the process ID
# in the name.  This directory is removed automatically at exit.
# -----------------------------------
tmpDir="/tmp/${scriptName}.$RANDOM.$RANDOM.$RANDOM.$$"
(umask 077 && mkdir "${tmpDir}") || {
  echo "Could not create temporary directory! Exiting."
  exit 1
}

# Logging
# -----------------------------------
# Log is only used when the '-l' flag is set.
#
# To never save a logfile change variable to '/dev/null'
# Save to Desktop use: $HOME/Desktop/${scriptBasename}.log
# Save to standard user log location use: $HOME/Library/Logs/${scriptBasename}.log
# -----------------------------------
logFile="$HOME/Library/Logs/${scriptBasename}.log"

# Check for Dependencies
# -----------------------------------
# Arrays containing package dependencies needed to execute this script.
# The script will fail if dependencies are not installed.  For Mac users,
# most dependencies can be installed automatically using the package
# manager 'Homebrew'.
# -----------------------------------
homebrewDependencies=()

function verbose() {
  if [[ $verbose -eq 1 ]]; then
    echo $1
  fi
}

function mainScript() {
  ############## Begin Script Here ###################
  ####################################################

  echo -n
  verbose "starting script"

  verbose "checking if required code param is set"
  if [[ $code == "" ]]; then
    verbose "exiting because required code param isn't set"
    echo "code or c is required"
    exit_do_to_missing_required_vars=1
  fi
  verbose "code param is set to ${code}"

  verbose "checking if required realm param is set"
  if [[ $realm == "" ]]; then
    verbose "exiting because required code param isn't set"
    echo "realm or r is required"
    exit_do_to_missing_required_vars=1
  fi
  verbose "realm param is set to ${realm}"

  verbose "checking to see if exit_do_to_missing_required_vars is 1"
  if [[ $exit_do_to_missing_required_vars -eq 1 ]]; then
    verbose "exit_do_to_missing_required_vars is 1 so exiting..."
    usage
    exit
  fi
  verbose "exit_do_to_missing_required_vars is not 1"

  verbose "setting region to: ${region}"
  region=$region
  aws configure set profile.${realm}.region $region

  verbose "setting username var: aws configure get username --profile $realm"
  username=$(aws configure get username --profile $realm)
  verbose "username is set to: ${username}"
  verbose "checking account number"
  account_number=$(aws configure get account_numbers.identity --profile $realm)
  verbose "account number is set to: ${account_number}"

  verbose "checking if required username aws config is set"
  if [[ $username == "" ]]; then
    verbose "exiting because required username aws config isn't set"
    echo "username is required to be set your realm's .aws/credentials profile"
    exit_do_to_missing_required_vars=1
  fi

  verbose "checking if required accounts and account_numbers aws config is set"
  if [[ $account_number == "" ]]; then
    verbose "exiting because required accounts and account_numbers aws config isn't set"
    echo "account_number is required to be set your realm's .aws/credentials profile"
    exit_do_to_missing_required_vars=1
  fi

  verbose "checking to see if exit_do_to_missing_required_vars is 1"
  if [[ $exit_do_to_missing_required_vars -eq 1 ]]; then
    verbose "exit_do_to_missing_required_vars is 1 so exiting..."
    usage
    exit
  fi

  verbose "creating MFA arn from account number and username"
  mfa_arn=arn:aws:iam::${account_number}:mfa/${username}
  verbose "mfa_arn = ${mfa_arn}"

  verbose "getting session token body by executing:"
  verbose "shell aws --profile=$realm sts get-session-token --serial-number $mfa_arn --token-code $code --duration-seconds $duration_seconds"
  return_body=$(aws --profile=$realm --region=$region sts get-session-token --serial-number $mfa_arn --token-code $code --duration-seconds $duration_seconds)
  verbose "session token body ="
  verbose $return_body
  verbose "getting keys from body"
  aws_session_token=$(echo $return_body | jq -r '.Credentials | .SessionToken')
  verbose "aws_session_token = ${aws_session_token}"
  secret_access_key=$(echo $return_body | jq -r '.Credentials | .SecretAccessKey')
  verbose "secret_access_key = ${secret_access_key}"
  access_key_id=$(echo $return_body | jq -r '.Credentials | .AccessKeyId')
  verbose "access_key_id = ${access_key_id}"

  if [[ $skip_key_rotate -eq 0 ]]; then
    verbose "skip key rotation not enabled: rotating key"
    return_body=""

    old_key_id=$(aws configure get aws_access_key_id --profile $realm)
    verbose "old key = ${old_key_id}"

    verbose "creating new access key"
    return_body=$(aws --profile=$realm iam create-access-key --user-name $username)
    verbose "return body ="
    verbose $return_body

    verbose "keys are:"
    new_key_id=$(echo $return_body | jq -r '.AccessKey | .AccessKeyId')
    verbose "new_key_id = ${new_key_id}"
    new_secret=$(echo $return_body | jq -r '.AccessKey | .SecretAccessKey')
    verbose "new_secret = ${new_secret}"

    verbose "deleting old access key"
    return_body=$(aws --profile=$realm iam delete-access-key --user-name $username --access-key-id $old_key_id)
    verbose "return body ="
    verbose $return_body

    verbose "setting aws_access_key_id"
    aws configure set profile.${realm}.aws_access_key_id $new_key_id
    verbose "setting aws_secret_access_key"
    aws configure set profile.${realm}.aws_secret_access_key $new_secret
  fi

  verbose ""
  verbose "SETTING MFA PROFILE"
  verbose "setting aws_access_key_id: aws configure set profile.${realm}_mfa.aws_access_key_id $access_key_id"
  aws configure set profile.${realm}_mfa.aws_access_key_id $access_key_id
  verbose "setting aws_secret_access_key: aws configure set profile.${realm}_mfa.aws_secret_access_key $secret_access_key"
  aws configure set profile.${realm}_mfa.aws_secret_access_key $secret_access_key
  verbose "setting aws_session_token: aws configure set profile.${realm}_mfa.aws_session_token $aws_session_token"
  aws configure set profile.${realm}_mfa.aws_session_token $aws_session_token
  verbose ""

  verbose "checking skip realm config is 0. it is = ${skip_realm_config}"
  if [[ $skip_realm_config -eq 0 ]]; then
    verbose "doing realm config"

    verbose "getting aws config for roles"
    return_body=$(aws configure get profile.${realm}.roles)
    verbose "return body ="
    verbose $return_body
    IFS=', ' read -r -a roles <<<"$return_body"

    for role in "${roles[@]}"; do
      verbose "role read: ${role}"
    done

    verbose "getting aws config for accounts"
    return_body=$(aws configure get profile.${realm}.accounts)
    verbose "return body ="
    verbose $return_body
    IFS=', ' read -r -a accounts <<<"$return_body"

    for account in "${accounts[@]}"; do
      verbose "getting account number from config for ${account}"
      account_number=$(aws configure get profile.${realm}.account_numbers.${account})
      verbose "account number is = ${account_number}"
      for role in "${roles[@]}"; do
        verbose "setting ${realm}_${account}_${role} source_profile = ${realm}_mfa"
        aws configure set profile.${realm}_${account}_${role}.source_profile ${realm}_mfa
        verbose "setting ${realm}_${account}_${role} role_arn = arn:aws:iam::${account_number}:role/${role}"
        aws configure set profile.${realm}_${account}_${role}.role_arn arn:aws:iam::${account_number}:role/${role}
      done
      if [[ $realm != "org_master" ]]; then
        verbose "linking account to org_master OrganizationAccountAccessRole profile"
        aws configure set profile.org_master_${realm}_${account}_OrganizationAccountAccessRole.source_profile org_master_mfa
        aws configure set profile.org_master_${realm}_${account}_OrganizationAccountAccessRole.role_arn arn:aws:iam::${account_number}:role/OrganizationAccountAccessRole
      fi
    done
  fi

  ####################################################
  ############### End Script Here ####################
}

############## Begin Options and Usage ###################

# Print usage
usage() {
  echo -n "${scriptName} [OPTION]... [FILE]...
This generates ~/.aws/credentials via the aws cli for mfa authentication.
username and account_numbers must be set in your realm's .aws/credentials profile.
Also, rotates your aws_access_key_id and secret key along with it each run unless you disable it.
Also, configures an entire realm based off of your ~/.aws/config and credentials. See README.md
 Options:
  -c, --code          required: Your rotating mfa code
  -r, --realm         required: The name of the realm. will result as realm_mfa as profile name
  -r, --region        change the region from default
  --skip-key-rotate   include this flag to skip the accesss key rotation
  --skip-realm-config include this flag to skip auto config of the entire realm in your ~/.aws/credentials file
  --duration-seconds  duration seconds the mfa is valid for. default is 129600 seconds(36 hr)
  -q, --quiet         Quiet (no output)
  -l, --log           Print log to file
  -s, --strict        Exit script with null variables.  i.e 'set -o nounset'
  -v, --verbose       Output more information. (Items echoed to 'verbose')
  -d, --debug         Runs script in BASH debug mode (set -x)
  -h, --help          Display this help and exit
      --version       Output version information and exit
"
}

# Iterate over options breaking -ab into -a -b when needed and --foo=bar into
# --foo bar
optstring=h
unset options
while (($#)); do
  case $1 in
  # If option is of type -ab
  -[!-]?*)
    # Loop over each character starting with the second
    for ((i = 1; i < ${#1}; i++)); do
      c=${1:i:1}

      # Add current char to options
      options+=("-$c")

      # If option takes a required argument, and it's not the last char make
      # the rest of the string its argument
      if [[ $optstring == *"$c:"* && ${1:i+1} ]]; then
        options+=("${1:i+1}")
        break
      fi
    done
    ;;

  # If option is of type --foo=bar
  --?*=*) options+=("${1%%=*}" "${1#*=}") ;;
  # add --endopts for --
  --) options+=(--endopts) ;;
  # Otherwise, nothing special
  *) options+=("$1") ;;
  esac
  shift
done
set -- "${options[@]}"
unset options

# Print help if no arguments were passed.
# Uncomment to force arguments when invoking the script
# [[ $# -eq 0 ]] && set -- "--help"

# Read the options and set stuff
while [[ $1 == -?* ]]; do
  case $1 in
  -c | --code)
    code=$2
    shift
    ;;
  -r | --realm)
    realm=$2
    shift
    ;;
  --region)
    region=$2
    shift
    ;;
  --mfa_arn)
    mfa_arn=$2
    shift
    ;;
  --duration-seconds)
    duration_seconds=$2
    shift
    ;;
  --skip-key-rotate) skip_key_rotate=1 ;;
  --skip-realm-config) skip_realm_config=1 ;;
  -h | --help)
    usage >&2
    exit 0
    ;;
  --version)
    echo "$(basename $0) ${version}"
    exit 0
    ;;
  -v | --verbose) verbose=1 ;;
  -l | --log) printLog=1 ;;
  -q | --quiet) quiet=1 ;;
  -s | --strict) strict=1 ;;
  -d | --debug) debug=1 ;;
  --force) force=1 ;;
  --endopts)
    shift
    break
    ;;
  *)
    echo "invalid option: '$1'."
    exit 1
    ;;
  esac
  shift
done

# Store the remaining part as arguments.
args+=("$@")

############## End Options and Usage ###################

# ############# ############# #############
# ##       TIME TO RUN THE SCRIPT        ##
# ##                                     ##
# ## You shouldn't need to edit anything ##
# ## beneath this line                   ##
# ##                                     ##
# ############# ############# #############

# Trap bad exits with your cleanup function
# trap trapCleanup EXIT INT TERM

# Exit on error. Append '||true' when you run the script if you expect an error.
set -o errexit

# Run in debug mode, if set
if [ "${debug}" == "1" ]; then
  set -x
fi

# Exit on empty variable
if [ "${strict}" == "1" ]; then
  set -o nounset
fi

# Bash will remember & return the highest exitcode in a chain of pipes.
# This way you can catch the error in case mysqldump fails in `mysqldump |gzip`, for example.
set -o pipefail

# Invoke the checkDependenices function to test for Bash packages
# checkDependencies

# Run your script
mainScript

# safeExit # Exit cleanly
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46402427

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档