hashicorp / terraform-provider-aws

The AWS Provider enables Terraform to manage AWS resources.
https://registry.terraform.io/providers/hashicorp/aws
Mozilla Public License 2.0
9.7k stars 9.07k forks source link

Doesn't ask MFA token code when using assume_role with MFA required #2420

Open jsi-p opened 6 years ago

jsi-p commented 6 years ago

When using multiple AWS accounts it's good practice to only allow access via AssumeRole from a master account. This can be done with or without requiring MFA. Terraform supports assume_role with s3 state file and aws provider configurations, but doesn't seem to ask the MFA token code when one is required. This prevents using AssumeRole for credentials when MFA is required.

AWS documentation describing MFA with cross account AssumeRole: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_mfa_configure-api-require.html#MFAProtectedAPI-cross-account-delegation

Terraform Version

$ terraform --version
Terraform v0.11.0

Affected Resource(s)

Both of these support assume_role, so they should also support asking for MFA token code:

Terraform Configuration Files

terraform {
  backend "s3" {
    bucket = "terraform-state-bucket"
    key = "tf-state"
    region = "eu-west-1"
    role_arn = "arn:aws:iam::916005212345:role/OrganizationAccountAccessRole"
  }
}
provider "aws" {
  region = "eu-west-1"
  assume_role {
    role_arn = "arn:aws:iam::916005212345:role/OrganizationAccountAccessRole"
    session_name = "terraform-session"
  }
}

Actual Behavior (with DEBUG)

$ TF_LOG=debug terraform init
2017/11/23 13:36:12 [INFO] Terraform version: 0.11.0  
2017/11/23 13:36:12 [INFO] Go runtime version: go1.9.2
2017/11/23 13:36:12 [INFO] CLI args: []string{"/usr/local/Cellar/terraform/0.11.0/bin/terraform", "init"}
2017/11/23 13:36:12 [DEBUG] Attempting to open CLI config file: /Users/***/.terraformrc
2017/11/23 13:36:12 [DEBUG] File doesn't exist, but doesn't need to. Ignoring.
2017/11/23 13:36:12 [INFO] CLI command args: []string{"init"}
2017/11/23 13:36:12 [DEBUG] command: loading backend config file: /Users/***

Initializing the backend...
2017/11/23 13:36:12 [WARN] command: backend config change! saved: 16375281725947963338, new: 2383462577283113429
Backend configuration changed!

Terraform has detected that the configuration specified for the backend
has changed. Terraform will now reconfigure for this backend. If you didn't
intend to reconfigure your backend please undo any changes to the "backend"
section in your Terraform configuration.

2017/11/23 13:36:12 [INFO] Building AWS region structure
2017/11/23 13:36:12 [INFO] Building AWS auth structure
2017/11/23 13:36:12 [INFO] Setting AWS metadata API timeout to 100ms
2017/11/23 13:36:13 [INFO] Ignoring AWS metadata API endpoint at default location as it doesn't return any instance-id
2017/11/23 13:36:13 [INFO] Attempting to AssumeRole arn:aws:iam::***:role/OrganizationAccountAccessRole (SessionName: "", ExternalId: "", Policy: "")
2017/11/23 13:36:13 [INFO] AWS Auth provider used: "SharedCredentialsProvider"
2017/11/23 13:36:13 [DEBUG] plugin: waiting for all plugin processes to complete...
Error initializing new backend: 
Error configuring the backend "s3": The role "arn:aws:iam::***:role/OrganizationAccountAccessRole" cannot be assumed.

  There are a number of possible causes of this - the most common are:
    * The credentials used in order to assume the role are invalid
    * The credentials do not have appropriate permission to assume the role
    * The role ARN is not valid

Please update the configuration in your Terraform files to fix this error
then run this command again.
jsi-p commented 6 years ago

This comment describes the same issue (while they workaround by not using MFA, which we do not want to do):

https://github.com/hashicorp/terraform/issues/11270#issuecomment-292179117

jsi-p commented 6 years ago

I would also like to see this work when simply using AWS_PROFILE env with the target profile configured to use a cross account role_arn and mfa_serial. In fact this would be the preferred way to use terraform for us.

$HOME/.aws/credentials:

[default]
aws_access_key_id = ***
aws_secret_access_key = ***

[subaccount]
role_arn = arn:aws:iam::***:role/OrganizationAccountAccessRole
source_profile = default
mfa_serial = arn:aws:iam::***:mfa/xxx@yyy.com

Terraform invocation:

AWS_PROFILE=subaccount terraform init
tamsky commented 6 years ago

FYI use of shared [profile]s via ~/.aws/config only landed a few days ago in:

https://github.com/terraform-providers/terraform-provider-aws/pull/1608

Comment from that PR:

This won't support assuming roles where an MFA device has been specified for the profile (in ~/.aws/config)

There was another implementation of #1608 -- it appears to include some (possibly limited) MFA support, but was closed in favor of 1608:

https://github.com/terraform-providers/terraform-provider-aws/pull/1590/files#diff-0460e055ea98f6ed9713f2265a1b1d49R266

Getting the go-sdk to prompt (once, and only once) for an MFA code would appear to involve a bit more work, but not much, by my reading of the comments.

tamsky commented 6 years ago

https://github.com/aws/aws-sdk-go/pull/1088 adds the StdinTokenProvider which we'd use.

tamsky commented 6 years ago

Also, caching of the temporary sts credentials in a portable cross-process manner might be an open issue: https://github.com/aws/aws-sdk-go/issues/1329

tamsky commented 6 years ago

awless's implementation of 15min temporary credential caching: https://github.com/wallix/awless/issues/109#issuecomment-314381704 in https://github.com/wallix/awless/commit/5a732239d7b8acaee4996b6f8197cf60b1a41872

danlsgiga commented 6 years ago

Just had the same issue... Have a profile with a role to be assumed and MFA enabled but it seems MFA is not supported at this moment. This would be a great addition to our setup.

We don't really need it to be prompted (probably should not even be considered)... working just like Packer works would be sufficient and that is having a mfa_code option where we can pass using -var or env vars.

https://www.packer.io/docs/builders/amazon-ebs.html#mfa_code

mikemoate commented 6 years ago

@tamsky I tried to add the MFA functionality into #1608 (we ideally wanted to be using MFA) but you are correct that the lack of an STS credentials cache essentially makes this unusable for the end user (repeated prompts for MFA token, and as tokens can only be used once, you have to wait until a new token is generated each time).

Since the inability to assume roles defined in the profile file was hurting us more, I elected to pursue that fix for now (still hasn't quite been released, see https://github.com/hashicorp/terraform/pull/16661), though I might return to the MFA issue when I next have some spare capacity (if someone else hasn't stepped in to solve it).

(the original PR https://github.com/hashicorp/terraform/pull/11734 that I resurrected to form #1608, had some discussion on MFA which led to me experimenting and finding the limitations)

apparentlymart commented 6 years ago

Hi all!

In general, Terraform doesn't currently support this sort of dynamic prompting for providers, and indeed the existing prompting features in Terraform are there primarily to help new users get started and we expect users to switch pretty early to using Terraform as a non-interactive, scripted tool, ideally running in a centrally-administered environment to avoid the need to sprawl various secrets across multiple user workstations.

Some users have reported success using a wrapper/helper script that calls sts:GetSessionToken to obtain temporary credentials in return for an MFA token, and then passes those temporary credentials to Terraform via environment variables, thus allowing that wrapper program to use whatever interactive means is appropriate (CLI prompt, web UI, etc) to collect that MFA token. This is, indeed, the methodology used by Terraform Enterprise for its AWS MFA support.

Managing authentication use-cases in wrapper scripts allows for other more advanced use-cases too, such as SAML authentication.

At this time we do not have plans to support interactive authentication to providers since it would require some significant changes to the provider model. We will probably look at this again in future in the context of some other breaking change to the provider model so that we can bundle multiple changes together, though as noted above we generally expect Terraform to be used non-interactively in some sort of automation once users pass the experimental phase, so any features we build in this area will need to also solve for a non-interactive workflow for that common case.

phil-hachey commented 6 years ago

For my purposes, I needed this feature for AWS org resource management across multiple accounts, so I can easily bootstrap new accounts. Another use case is for DevOps to manage resources across accounts originating from a "security" account (as described here). In all scenarios, MFA should be enabled for cross account access (internal requirement).

I ended up getting this to work by requesting temporary credentials using MFA and saving them to ~/.aws/credentials. With these credentials, the "MFA required" condition has been met and you're free to use the assume_role block of the aws provider in Terraform:

provider "aws" {
  alias  = "dev_account"
  region = "${var.aws_region}"

  assume_role {
    role_arn = "${var.dev_account_target_role_arn}"
  }
}

I created a simple python cli to generate the creds: https://github.com/phil-hachey/aws-mfa-tool

scalp42 commented 6 years ago

Has anyone tried working around the issue by using something like https://github.com/remind101/assume-role ?

kmaris commented 6 years ago

@scalp42 assume-role works. I also used a smaller and simpler script to do about the same: https://gitlab.com/kmaris/wtf (The name is terrible, I know).

andreas-venturini commented 6 years ago

I ended up getting this to work by requesting temporary credentials using MFA and saving them to ~/.aws/credentials

I suggest aws-vault for this purpose as it is much more convenient. aws-vault stores your long-term access credentials encrypted and will deal with exposing temporary AWS access credentials to your shell or application so you don't have to. As a matter of fact, you can delete your credentials file and manage AWS API access entirely w/ aws-vault and named profiles defined in ~/.aws/config.

I'll show an example that uses cross account IAM role delegation for executing terraform commands. OrganizationAccountAccessRole is the IAM role that authorized IAM users from an AWS master account can assume. The trust policy of the IAM role enforces MFA (set aws:MultiFactorAuthPresent boolean flag as described here)

The terraform AWS provider config:

provider "aws" {
  profile = "${var.aws_vault_profile}"
  region  = "${var.aws_region}"
  version = "~> 1.30.0"

  assume_role {
    role_arn = "arn:aws:iam::${var.aws_app_account_id}:role/OrganizationAccountAccessRole"
  }
}

Here is the corresponding ~/.aws/config file. It defines a terraform profile which uses MFA (the [profile ...] block is automatically created by aws-vault when adding a new profile - you just have to fill in the arn of the MFA device).

[default]
aws_region=eu-central-1

[profile terraform]
mfa_serial=arn:aws:iam::123456789012:mfa/Andreas

Then simply use aws-vault exec to wrap the terraform command - or set up an alias like so (note to change the backend depending on your system environment - this example uses the secret service API w/ keyring which works well for me on Ubuntu):

# always use aws-vault to wrap terraform command
alias terraform='aws-vault exec terraform --backend=secret-service -- terraform'

When you run terraform you will be automatically prompted for the access token:

➜  terraform-setup (master) ✗ terraform workspace list                                                          
Enter token for arn:aws:iam::123456789012:mfa/Andreas: 

Of course you can also use multiple profiles w/ aws-vault. There are more advanced configuration examples in the aws-vault docs.

jonscheiding commented 5 years ago

We can work around this using aws-vault or something similar, but I'd love to see the idea of interactive MFA authentication revisited (both for AWS and for other cloud providers where applicable).

I strongly disagree with @apparentlymart's rationale from 2 years ago. While iterating on a TF configuration, I might terraform apply it against our dev account dozens of times over the course of a day. Doing that through our build/deploy pipeline is an obvious non-starter.

Of course we use that pipeline for deployment to environments after dev. So it's not a matter of using TF interactively to manage our infrastructure. But we're contractually obligated to enforce MFA even in our dev account. (And we use account federation with centralized IAM management so it's not really possible to selectively enforce it anyway.) So as is, it makes our dev workflow more complex.

I can't imagine our workflow or our requirements are particularly unusual, so it seems like a high value add for TF to support it.

stumyp commented 4 years ago

My 2 cents:

AWS_MFA_TOTP=123456 terraform apply

so if assumed role requires MFA we can work around it by using variable and if that role is not protected with MFA, it will not be used.

Thanks

yn-academia commented 4 years ago

There is no need to prompt for Terraform to support asking for a MFA value. If you run AWS_PROFILE=profile_that_requires_mfa aws iam list-users from the AWS CLI tool, and enter your token into the CLI tool, you will then find your temporary credentials cached in ~/.aws/cli/cache, so Terraform could just use that, and keep around the requirement that terraform is non-interactive.

yobert commented 4 years ago

Re @stumyp

  • While I understand the point of non-interactive flow. I'd really like to see the possibility of using environment variable for specifying MFA value, something like this:

The problem with this is that an MFA token is only allowed to be used once in AWS and so this would effectively limit terraform to only one API call every 30 seconds.

stumyp commented 4 years ago

The problem with this is that an MFA token is only allowed to be used once in AWS and so this would effectively limit terraform to only one API call every 30 seconds.

This is incorrect: once temporary session is obtained it can lasts much longer. see assume role API call

yobert commented 4 years ago

The problem with this is that an MFA token is only allowed to be used once in AWS and so this would effectively limit terraform to only one API call every 30 seconds.

This is incorrect: once temporary session is obtained it can lasts much longer. see assume role API call

Aha I see what your saying. I thought you meant an environment variable for the MFA code would be sufficient to replace the temporary credential storage workflow. My bad!

sir-farfan commented 4 years ago

I got here because I also need Terraform to ask the MFA key before assuming the role. Guess it's still not possible...

dac73 commented 4 years ago

I found some old script and updated it a bit for a workaround, if anyone needs. It basicaly creates temporary credentials profile with assumed role. https://github.com/dac73/terraform-session-token

stumyp commented 4 years ago

The goal is not be able to use assumed role with MFA enforcement. The goal is to be able to use it in terraform, along with other roles and/or main role used for this assumption.

Example1:

Example2:

The manual assume_role is not so hard to do:

#!/bin/bash

set -e

TMP="/tmp/$$-assumedrole"
AWS=`command -v aws`
DURATION=14400

usage() {
    cat <<EOH
Usage: $0 -r role_arn [-n session name] [-t mfa_token [-m mfa_device_index]]

Where:
  role_arn: the ARN of the role you want to assume
  session name: an optional name of the session that is opened by this script in STS. Defaults to "from_the_script"
  mfa_token: TOTP code from MFA
  mfa_device_index: If you have more than one MFA (especial hardware one) number of the device. Defaults to "0"
EOH
exit 0
}
while getopts "r:n:t:m:d:" arg ; do
    case $arg in
        r)
            ROLE=$OPTARG
        ;;
        n)
            SESSION_NAME=$OPTARG
        ;;
        t)
            TOKEN="$OPTARG"
        ;;
        m)
            MFA=$OPTARG
        ;;
        d)
            DURATION=$OPTARG
        ;;
    esac
done

[ -n "$ROLE" ] || usage 
[ -n "$SESSION_NAME" ] || SESSION_NAME="from_the_script"

if [ -n "$TOKEN" ] ; then
    [ -n "$MFA" ] || MFA="0"
    MFA_SERIAL=$($AWS iam list-mfa-devices | jq -r '.MFADevices['$MFA'].SerialNumber')
    EXTRAOPTS="--token-code $TOKEN --serial-number $MFA_SERIAL"
fi

$AWS  sts assume-role --role-arn $ROLE --role-session-name $SESSION_NAME --duration-seconds $DURATION $EXTRAOPTS >$TMP

export AWS_ACCESS_KEY_ID=`jq -r '.Credentials.AccessKeyId' <$TMP`
export AWS_SECRET_ACCESS_KEY=`jq -r '.Credentials.SecretAccessKey' <$TMP`
export AWS_SESSION_TOKEN=`jq -r '.Credentials.SessionToken' <$TMP`

echo "export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID"
echo "export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY"
echo "export AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN"
rm -r $TMP
yn-academia commented 4 years ago

@stumyp If you are willing to write multi-line shell scripts, then I think what you want is possible by installing aws-vault, then generating multiple sessions ahead of time, and then running terraform and from inside terraform shelling out to aws-vault as an external provider and configuring the terraform-aws provider with the credentials that aws-vault returns back to you.

stumyp commented 4 years ago

@yn-academia If you don't need this specifc feature, it doesn't mean other people don't need it as well.

This multiline script was made for single switch between main role and assumed role. one way, no way back. In terraform I want to work with multiple accounts easily. Which I do right now, but without MFA. This weakens our security policy. I want to make my work more secure and do not overcomplicate it at the same time.

dac73 commented 4 years ago

@stumyp I have exactly that problem, and what my solution does is it creates tf profiles with an assumed role in ~/.aws/credentials so I can then use named profiles in terraform code. So basically I run the script against multiple assumed roles once and then I can use terraform across multiple accounts/assumed roles (until tokens expire). It's not a solution for only one account/assumed role.

stumyp commented 4 years ago

Thank you, @dac73, I was not looking for the workaround.

I'm just adding one more voice to implement fully STS AssumeRole API call in Terraform. Not in the way it is done right now.

Currently TF supports only three input parameters:

    role_arn     = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
    session_name = "SESSION_NAME"
    external_id  = "EXTERNAL_ID"

while API call accepts more.:

Probably it should be a separate issue to add the rest of the parameters, but I would love to see there

Let the users deal with how they want to provide those values to TF, just add support for it.

stumyp commented 4 years ago

@apparentlymart should I create separate issue for what I'm asking for, since this particular one is about dynamic prompting for MFA value?

lapkritinis commented 4 years ago

I support requesting for MFA input. Profiles, etc are good for single provider, but thats not always the case. For example I have a need to build VPC peering between two accounts. Each of them requires MFA. Unless I setup some cross account trust in AWS - there is no way to achieve that using environment variables as all of them - will be for single account only.

mdforbes500 commented 4 years ago

Also here to support implementing MFA input. When implementing AWS CLI accounts, MFA is a must here.

scalp42 commented 3 years ago

@andreas-venturini the issue is that yes aws-vault will work but in our scenario we have to consume the state in different accounts.

Assuming you have 2 accounts prod and dev with respective states, S3 buckets and credentials/roles:

data "terraform_remote_state" "prod_us_west_2" {
  backend = "s3"

  config = {
    profile = "prod"
    bucket  = "terraform"
    key     = "prod/us-west-2/main.tfstate"
    region  = "us-west-2"
  }
}

data "terraform_remote_state" "dev_us_west_2" {
  backend = "s3"

  config = {
    profile = "dev"
    bucket  = "terraform"
    key     = "dev/us-west-2/main.tfstate"
    region  = "us-west-2"
  }
}

If you wrap the terraform command:

aws-vault exec prod -- terraform plan

It'll work as long as you don't have a provider that references a state from a different account if it makes sense as it'll assume whatever role in the prod account but then try to access the dev account as the assumed role if it makes sense and will fail.

Without MFA/aws-vault, it'll work as Terraform can handle multiple sessions per providers.

olfway commented 3 years ago

Different accounts can be used with aws-vault as a credential_process for aws sdk, ie

~/.aws/config

[profile root]
mfa_serial = arn:aws:iam::XXX:mfa/olfway
credential_process = env AWS_SDK_LOAD_CONFIG=0 aws-vault --backend=pass --pass-prefix=aws-vault --prompt=osascript exec --region eu-west-1 --duration=1h root --json

[profile prod]
source_profile = root
parent_profile = root
role_arn = arn:aws:iam::YYY:role/Prod

[profile dev]
source_profile = root
parent_profile = root
role_arn = arn:aws:iam::ZZZ:role/Dev

and then run terraform as

export AWS_SDK_LOAD_CONFIG=1
terraform plan
scalp42 commented 3 years ago

@olfway I tried to replicate the same set up but I'm running into the following:

Error: error configuring S3 Backend: Error creating AWS session: AssumeRoleTokenProviderNotSetError: assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.

What does your provider configuration looks like?

olfway commented 3 years ago
provider "aws" {
  profile = "prod"
  region  = "eu-west-1"
  version = "3.4.0"
}
terraform {
  required_version = "0.13.2"
  backend "s3" {
    profile        = "prod"
    region         = "eu-west-1"
    dynamodb_table = "terraform-locks"
    bucket         = "prod-terraform-states"
  }
}

Also you could check with

export AWS_SDK_LOAD_CONFIG=1
aws --profile prod sts get-caller-identity

it should use credentials from aws-vault and ask for mfa only once

or run aws-vault directly:

env AWS_SDK_LOAD_CONFIG=0 aws-vault --some-backend-options --prompt=osascript exec --region eu-west-1 --duration=1h root --json
imaginarynik commented 3 years ago

Has there already been an update on this? Any good solutions apart from aws-vault?

o6uoq commented 3 years ago

https://awsu.me/

MikeRippon commented 3 years ago

This feels like a glaring omission. While being a "non-interactive, scripted tool" is a great primary goal, expecting everything to run in an automated pipeline is unrealistic, especially when terraform has commands such as destroy, force-unlock, import, refresh, state, taint, untaint, which only make sense to be invoked manually. Not to mention that the CLI already prompts for user input if a variable isn't set (for example).

Occasionally you need an escape hatch to deal with edge cases, and how many people don't have MFA enabled for live environments?

I'm forced to choose between turning off MFA (non-option), or adding third-party tooling/custom scripts (which increases maintenance, cognitive overhead, and learning curves for new developers). I'm trying to reduce the complexity of infrastructure deployments, not increase it.

Thankfully I'm thinking of this in advance and making sure there's a solution in place for my client. I feel sorry for anyone that relies on their tf pipeline, and then ends up screwed because something unexpected happened in live, and they can't fix it just because terraform can't ask them for an MFA code.

rwiggins commented 3 years ago

For anyone who's just looking for a simple escape hatch: There are plenty of programs that manage this problem better, as written above, but honestly, I've used this incredibly simple bash script for years, although I call it exec-in-aws-context instead of aws-assume-role. Basically, if you are using an AWS CLI profile that has a source profile configured, it sets up the AssumeRole-driven session using the AWS CLI, then grabs the temporary credentials and shoves them into the "basic" envvars (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY). In other words, the invoked program (e.g. Terraform) doesn't need to support the full suite of AWS CLI profile features. From the program's perspective, it's just regular AWS credentials.

For example:

Place the script somewhere on your path, e.g. ~/bin/exec-in-aws-context. Make sure you're using an AWS_PROFILE that has a source_profile in ~/.aws/config. Invoke Terraform like

exec-in-aws-context terraform apply

The script will call the AWS CLI and set up the temporary session (which will prompt you for MFA), then invoke terraform apply with the "simple" envvars instead of AWS_PROFILE.

I've actually found that little bash script very useful over the years. I prefer it over some I've seen because it just goes through the regular AWS CLI flow instead of re-implementing part(s) of it in bash. Additionally, it doesn't mess with your existing shell session envvars.

ghomem commented 2 years ago

I have been reading this ticket and would like to clarify if I understand it correctly.

Using this:

provider "aws" {
  region = "eu-west-2"
  access_key = var.aws_key
  secret_key = var.aws_secret
}

Terraform prompts for the key and secret which come straight away from a password management tool with encrypted storage.

In case the user has MFA enabled would it be possible that terraform also prompts for the TOTP code? Is there any intention to support this on terraform or is this not considered an interesting scenario?

Thanks! :-)

prmarino1m commented 2 years ago

if I'm getting this right the reason we cant have an interactive MFA prompt is because its not compatible with automation. i disagree with the premise that automation of running terraform is always required. if automation is needed then TOTP codes are not hard to get working, TOTP authenticators are programmed with a string so all that's needed is a field that can be added to calculate the code from a string, however I would be remis if I didn't state that not prompting is a mistake because it circumvents Information security requirements So the solution might be to have an option in the provider that lets you choose between prompting the user for MFA, Not prompting the user for MFA, or calculating the MFA from the string. this would satisfy everyone. Also this is one of several massive issues with AWS govcloud support.

prmarino1m commented 2 years ago

By the way despite seatmates to the contrary in other tickets the AWS CLI does cache session tokens for assume role operations see https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html#cli-configure-role-mfa for details so an other option would be to allow terraform to use the cached credentials. if it used the cache then users could just do a simple aws sts get-caller-identity against the profile and they would be prompted for their MFA then terraform could used the cached credentials

boillodmanuel commented 2 years ago

If you want to run a terraform project that requires MFA, this is possible. Before running terraform, you can get a session token with MFA and then use its credentials to run terraform. This is explained here: https://aws.amazon.com/premiumsupport/knowledge-center/authenticate-mfa-cli/

The pseudo code is:

# Get an AWS session
# NOTE: MFA_CODE is a fresh CODE given by your MFA virtual device, like google authenticator. This code change so you can't automate this command
aws sts get-session-token --serial-number "${MFA_DEVICE_SERIAL_NUMBER}" --token-code "${MFA_CODE}"

# The credentials of the session are in the output of the previous command
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_SESSION_TOKEN=

# Run terraform
terraform apply

The following script allows you to get the MFA_DEVICE_SERIAL_NUMBER:

CURRENT_USER_ARN="$(aws sts get-caller-identity --query Arn --output text)
MFA_DEVICE_SERIAL_NUMBER="$(aws iam list-virtual-mfa-devices --query "VirtualMFADevices[?User.Arn=='${CURRENT_USER_ARN}'] | [0].SerialNumber" --output text)"

As said, you have to get the MFA_CODE manually.

gdavison commented 1 year ago

Additional discussion on this issue, including workarounds used by some practitioners, can be found at https://github.com/hashicorp/terraform-provider-aws/issues/10491

gdavison commented 1 year ago

Hello everyone, and thank you for your patience on this long-standing issue.

As @apparentlymart mentioned in https://github.com/hashicorp/terraform-provider-aws/issues/2420#issuecomment-352518083, the expectation has been that Terraform will typically be used in a non-interactive mode. While that is generally still the sentiment in the Terraform CLI team, we are having some discussions internally around how to allow some interactivity.

The Terraform CLI repository is the best place to request interactivity for Terraform. This will have to be a general solution that can address needs for any providers that would need this. It would also have to act "reasonably" in the case where Terraform is running non-interactively and a provider requests interaction.

There have been a few workarounds and proposed solutions listed in this issue.

  1. Using external tools (aws-vault seems to be the most popular) to perform the authentication before starting Terraform. This appears to work well for some practitioners, especially when the tool "wraps" calls to Terraform, so that the tool can manage renewing the credentials.
  2. Using a script to create temporary credentials in the ~/.aws/credentials file. This will work, but will not be able to renew the credentials. Thus, the session duration needs to be long enough to complete the Terraform operations.

AWS profiles also support the use of an External Credentials Process to authenticate. The AWS Provider currently supports this with profiles in the ~/.aws/config file. We have an open issue, #24885, to add this to the provider configuration block. In both cases, however, the process must be able to prompt the user without using the console, since it is not available from within the provider. aws-vault appears to be able to do this on macOS using the flag --prompt=osascript, but I have not seen examples for other platforms.

Another suggestion was to make use of the AWS CLI credentials cache. The AWS CLI documents the location of the cache, but doesn't document the format. While the format is the format used by the Boto python library, because it isn't stated in the CLI documentation, it is not guaranteed not to change. We feel that this approach is too brittle for inclusion in the provider.

We're going to add the external credentials process to the provider for parity with the credentials and configuration files. We understand that this is not a perfect solution for the MFA issue. Without the ability to interact with the practitioner, the provider cannot solve the MFA issue by itself. Some of the other workarounds or tools may work for you in conjunction with the options supported by the provider.

olivers-xaxis commented 9 months ago

For anyone who's just looking for a simple escape hatch: There are plenty of programs that manage this problem better, as written above, but honestly, I've used this incredibly simple bash script for years, although I call it exec-in-aws-context instead of aws-assume-role. Basically, if you are using an AWS CLI profile that has a source profile configured, i

This link is dead, does anyone have the source?

CaerusKaru commented 7 months ago

Now that https://github.com/aws/aws-cli/issues/3711 has been merged, this is very trivial to implement for any non-S3 backend processes. This is assuming you already have an AWS config profile that has MFA and AssumeRole configured.

Add a new profile using credential_process dedicated to Terraform, e.g. assuming you have a profile existing already called "myprofile":

[profile myprofile-tf]
credential_process = aws configure export-credentials --profile myprofile

The CLI will prompt for the MFA code in the process of running terraform, and will cache the result, taking Terraform out of the equation entirely.

I'm not fully sure why this doesn't work for the S3 backend just specifying profile in the provider, but adding AWS_PROFILE as a command-line environment variable worked for me on terraform init.

Once the feature @gdavison mentioned above lands for configuring this from the Terraform provider itself, the extra profile will not be necessary.