Skip to content

Commit

Permalink
Add support for STS duration option (#1673)
Browse files Browse the repository at this point in the history
* Add support for STS duration option

Add support for STS duration option

Add support for STS duration option

Add support for STS duration option

* Fix aggressive formatting

* Address comments on doc

* Remove leftover debugging statement

* Address more review comments

* fix doc

* Update Clone method for stsDuration

* Also update new

* Rename sts-duration to iam-assume-role-duration

* Add const for defaultIamAssumeRoleDuration

* add forgotten const

* small fixes

* Fix indentation

* Re-use const defined in options!
  • Loading branch information
mantoine96 authored May 20, 2021
1 parent 9e59ad5 commit daabff2
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 5 deletions.
7 changes: 4 additions & 3 deletions aws_helper/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func CreateAwsSession(config *AwsSessionConfig, terragruntOptions *options.Terra
}

// Make API calls to AWS to assume the IAM role specified and return the temporary AWS credentials to use that role
func AssumeIamRole(iamRoleArn string) (*sts.Credentials, error) {
func AssumeIamRole(iamRoleArn string, sessionDurationSeconds int64) (*sts.Credentials, error) {
sess, err := session.NewSession()
if err != nil {
return nil, errors.WithStackTrace(err)
Expand All @@ -140,6 +140,7 @@ func AssumeIamRole(iamRoleArn string) (*sts.Credentials, error) {
input := sts.AssumeRoleInput{
RoleArn: aws.String(iamRoleArn),
RoleSessionName: aws.String(fmt.Sprintf("terragrunt-%d", time.Now().UTC().UnixNano())),
DurationSeconds: aws.Int64(sessionDurationSeconds),
}

output, err := stsClient.AssumeRole(&input)
Expand Down Expand Up @@ -202,8 +203,8 @@ func AssumeRoleAndUpdateEnvIfNecessary(terragruntOptions *options.TerragruntOpti
return nil
}

terragruntOptions.Logger.Debugf("Assuming IAM role %s", terragruntOptions.IamRole)
creds, err := AssumeIamRole(terragruntOptions.IamRole)
terragruntOptions.Logger.Debugf("Assuming IAM role %s with a session duration of %d seconds.", terragruntOptions.IamRole, terragruntOptions.IamAssumeRoleDuration)
creds, err := AssumeIamRole(terragruntOptions.IamRole, terragruntOptions.IamAssumeRoleDuration)
if err != nil {
return err
}
Expand Down
9 changes: 8 additions & 1 deletion cli/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ func parseTerragruntOptionsFromArgs(terragruntVersion string, args []string, wri
return nil, err
}

envValue, envProvided := os.LookupEnv("TERRAGRUNT_IAM_ASSUME_ROLE_DURATION")
IamAssumeRoleDuration, err := parseIntArg(args, OPT_TERRAGRUNT_IAM_ASSUME_ROLE_DURATION, envValue, envProvided, options.DEFAULT_IAM_ASSUME_ROLE_DURATION)
if err != nil {
return nil, err
}

excludeDirs, err := parseMultiStringArg(args, OPT_TERRAGRUNT_EXCLUDE_DIR, []string{})
if err != nil {
return nil, err
Expand Down Expand Up @@ -151,7 +157,7 @@ func parseTerragruntOptionsFromArgs(terragruntVersion string, args []string, wri
opts.Debug = true
}

envValue, envProvided := os.LookupEnv("TERRAGRUNT_PARALLELISM")
envValue, envProvided = os.LookupEnv("TERRAGRUNT_PARALLELISM")
parallelism, err := parseIntArg(args, OPT_TERRAGRUNT_PARALLELISM, envValue, envProvided, options.DEFAULT_PARALLELISM)
if err != nil {
return nil, err
Expand Down Expand Up @@ -189,6 +195,7 @@ func parseTerragruntOptionsFromArgs(terragruntVersion string, args []string, wri
opts.ErrWriter = errWriter
opts.Env = parseEnvironmentVariables(os.Environ())
opts.IamRole = iamRole
opts.IamAssumeRoleDuration = int64(IamAssumeRoleDuration)
opts.ExcludeDirs = excludeDirs
opts.IncludeDirs = includeDirs
opts.StrictInclude = strictInclude
Expand Down
12 changes: 12 additions & 0 deletions cli/args_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ func TestParseTerragruntOptionsFromArgs(t *testing.T) {
nil,
},

{
[]string{"--terragrunt-iam-assume-role-duration", "36000"},
mockOptionsWithIamAssumeRoleDuration(t, util.JoinPath(workingDir, config.DefaultTerragruntConfigPath), workingDir, []string{}, false, "", false, 36000),
nil,
},

{
[]string{"--terragrunt-config", fmt.Sprintf("/some/path/%s", config.DefaultTerragruntConfigPath), "--terragrunt-non-interactive"},
mockOptions(t, fmt.Sprintf("/some/path/%s", config.DefaultTerragruntConfigPath), workingDir, []string{}, true, "", false, false, defaultLogLevel, false),
Expand Down Expand Up @@ -211,6 +217,12 @@ func mockOptionsWithIamRole(t *testing.T, terragruntConfigPath string, workingDi
return opts
}

func mockOptionsWithIamAssumeRoleDuration(t *testing.T, terragruntConfigPath string, workingDir string, terraformCliArgs []string, nonInteractive bool, terragruntSource string, ignoreDependencyErrors bool, IamAssumeRoleDuration int64) *options.TerragruntOptions {
opts := mockOptions(t, terragruntConfigPath, workingDir, terraformCliArgs, nonInteractive, terragruntSource, ignoreDependencyErrors, false, defaultLogLevel, false)
opts.IamAssumeRoleDuration = IamAssumeRoleDuration

return opts
}
func mockOptionsWithOverrideAttrs(t *testing.T, terragruntConfigPath string, workingDir string, terraformCliArgs []string, overrideAttrs map[string]string) *options.TerragruntOptions {
opts := mockOptions(t, terragruntConfigPath, workingDir, terraformCliArgs, false, "", false, false, defaultLogLevel, false)
opts.AwsProviderPatchOverrides = overrideAttrs
Expand Down
8 changes: 8 additions & 0 deletions cli/cli_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const OPT_TERRAGRUNT_SOURCE = "terragrunt-source"
const OPT_TERRAGRUNT_SOURCE_MAP = "terragrunt-source-map"
const OPT_TERRAGRUNT_SOURCE_UPDATE = "terragrunt-source-update"
const OPT_TERRAGRUNT_IAM_ROLE = "terragrunt-iam-role"
const OPT_TERRAGRUNT_IAM_ASSUME_ROLE_DURATION = "terragrunt-iam-assume-role-duration"
const OPT_TERRAGRUNT_IGNORE_DEPENDENCY_ERRORS = "terragrunt-ignore-dependency-errors"
const OPT_TERRAGRUNT_IGNORE_DEPENDENCY_ORDER = "terragrunt-ignore-dependency-order"
const OPT_TERRAGRUNT_IGNORE_EXTERNAL_DEPENDENCIES = "terragrunt-ignore-external-dependencies"
Expand Down Expand Up @@ -70,6 +71,7 @@ var ALL_TERRAGRUNT_STRING_OPTS = []string{
OPT_TERRAGRUNT_SOURCE,
OPT_TERRAGRUNT_SOURCE_MAP,
OPT_TERRAGRUNT_IAM_ROLE,
OPT_TERRAGRUNT_IAM_ASSUME_ROLE_DURATION,
OPT_TERRAGRUNT_EXCLUDE_DIR,
OPT_TERRAGRUNT_INCLUDE_DIR,
OPT_TERRAGRUNT_PARALLELISM,
Expand Down Expand Up @@ -219,6 +221,7 @@ GLOBAL OPTIONS:
terragrunt-source Download Terraform configurations from the specified source into a temporary folder, and run Terraform in that temporary folder.
terragrunt-source-update Delete the contents of the temporary folder to clear out any old, cached source code before downloading new source code into it.
terragrunt-iam-role Assume the specified IAM role before executing Terraform. Can also be set via the TERRAGRUNT_IAM_ROLE environment variable.
terragrunt-iam-assume-role-duration Session duration for IAM Assume Role session. Can also be set via the TERRAGRUNT_IAM_ASSUME_ROLE_DURATION environment variable.
terragrunt-ignore-dependency-errors *-all commands continue processing components even if a dependency fails.
terragrunt-ignore-dependency-order *-all commands will be run disregarding the dependencies
terragrunt-ignore-external-dependencies *-all commands will not attempt to include external dependencies
Expand Down Expand Up @@ -372,6 +375,11 @@ func RunTerragrunt(terragruntOptions *options.TerragruntOptions) error {
terragruntOptions.IamRole = terragruntConfig.IamRole
}

// replace default sts duration if set in config
if terragruntOptions.IamAssumeRoleDuration == int64(options.DEFAULT_IAM_ASSUME_ROLE_DURATION) && terragruntConfig.IamAssumeRoleDuration != nil {
terragruntOptions.IamAssumeRoleDuration = *terragruntConfig.IamAssumeRoleDuration
}

if err := aws_helper.AssumeRoleAndUpdateEnvIfNecessary(terragruntOptions); err != nil {
return err
}
Expand Down
10 changes: 10 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type TerragruntConfig struct {
PreventDestroy *bool
Skip bool
IamRole string
IamAssumeRoleDuration *int64
Inputs map[string]interface{}
Locals map[string]interface{}
TerragruntDependencies []Dependency
Expand Down Expand Up @@ -85,6 +86,7 @@ type terragruntConfigFile struct {
PreventDestroy *bool `hcl:"prevent_destroy,attr"`
Skip *bool `hcl:"skip,attr"`
IamRole *string `hcl:"iam_role,attr"`
IamAssumeRoleDuration *int64 `hcl:"iam_assume_role_duration,attr"`
TerragruntDependencies []Dependency `hcl:"dependency,block"`

// We allow users to configure code generation via blocks:
Expand Down Expand Up @@ -660,6 +662,10 @@ func mergeConfigWithIncludedConfig(config *TerragruntConfig, includedConfig *Ter
includedConfig.IamRole = config.IamRole
}

if config.IamAssumeRoleDuration != nil {
includedConfig.IamAssumeRoleDuration = config.IamAssumeRoleDuration
}

if config.TerraformVersionConstraint != "" {
includedConfig.TerraformVersionConstraint = config.TerraformVersionConstraint
}
Expand Down Expand Up @@ -893,6 +899,10 @@ func convertToTerragruntConfig(
terragruntConfig.IamRole = *terragruntConfigFromFile.IamRole
}

if terragruntConfigFromFile.IamAssumeRoleDuration != nil {
terragruntConfig.IamAssumeRoleDuration = terragruntConfigFromFile.IamAssumeRoleDuration
}

generateBlocks := []terragruntGenerateBlock{}
generateBlocks = append(generateBlocks, terragruntConfigFromFile.GenerateBlocks...)

Expand Down
10 changes: 9 additions & 1 deletion config/config_as_cty.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"encoding/json"

"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
ctyjson "github.com/zclconf/go-cty/cty/json"
Expand Down Expand Up @@ -79,6 +78,15 @@ func terragruntConfigAsCty(config *TerragruntConfig) (cty.Value, error) {
output["retryable_errors"] = retryableCty
}

iamAssumeRoleDurationCty, err := goTypeToCty(config.IamAssumeRoleDuration)
if err != nil {
return cty.NilVal, err
}

if iamAssumeRoleDurationCty != cty.NilVal {
output["iam_assume_role_duration"] = iamAssumeRoleDurationCty
}

retryMaxAttemptsCty, err := goTypeToCty(config.RetryMaxAttempts)
if err != nil {
return cty.NilVal, err
Expand Down
2 changes: 2 additions & 0 deletions config/config_as_cty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,8 @@ func terragruntConfigStructFieldToMapKey(t *testing.T, fieldName string) (string
return "skip", true
case "IamRole":
return "iam_role", true
case "IamAssumeRoleDuration":
return "iam_assume_role_duration", true
case "Inputs":
return "inputs", true
case "Locals":
Expand Down
18 changes: 18 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,24 @@ func TestParseIamRole(t *testing.T) {
assert.Equal(t, "terragrunt-iam-role", terragruntConfig.IamRole)
}

func TestParseIamAssumeRoleDuration(t *testing.T) {
t.Parallel()

config := `iam_assume_role_duration = 36000`

terragruntConfig, err := ParseConfigString(config, mockOptionsForTest(t), nil, DefaultTerragruntConfigPath)
if err != nil {
t.Fatal(err)
}

assert.Nil(t, terragruntConfig.RemoteState)
assert.Nil(t, terragruntConfig.Terraform)
assert.Nil(t, terragruntConfig.Dependencies)
assert.Nil(t, terragruntConfig.RetryableErrors)

assert.Equal(t, int64(36000), *terragruntConfig.IamAssumeRoleDuration)
}

func TestParseTerragruntConfigDependenciesOnePath(t *testing.T) {
t.Parallel()

Expand Down
10 changes: 10 additions & 0 deletions docs/_docs/04_reference/cli-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ prefix `--terragrunt-` (e.g., `--terragrunt-config`). The currently available op
- [terragrunt-source-update](#terragrunt-source-update)
- [terragrunt-ignore-dependency-errors](#terragrunt-ignore-dependency-errors)
- [terragrunt-iam-role](#terragrunt-iam-role)
- [terragrunt-iam-assume-role-duration](#terragrunt-iam-assume-role-duration)
- [terragrunt-exclude-dir](#terragrunt-exclude-dir)
- [terragrunt-include-dir](#terragrunt-include-dir)
- [terragrunt-strict-include](#terragrunt-strict-include)
Expand Down Expand Up @@ -556,6 +557,15 @@ Assume the specified IAM role ARN before running Terraform or AWS commands. This
and Terraform with multiple AWS accounts.


### terragrunt-iam-assume-role-duration

**CLI Arg**: `--terragrunt-iam-assume-role-duration`<br/>
**Environment Variable**: `TERRAGRUNT_IAM_ASSUME_ROLE_DURATION`<br/>
**Requires an argument**: `--terragrunt-iam-assume-role-duration 3600`

Uses the specified duration as the session duration (in seconds) for the STS session which assumes the role defined in `--terragrunt-iam-role`.


### terragrunt-exclude-dir

**CLI Arg**: `--terragrunt-exclude-dir`<br/>
Expand Down
16 changes: 16 additions & 0 deletions docs/_docs/04_reference/config-blocks-and-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@ generate = local.common.generate
- [prevent_destroy](#prevent_destroy)
- [skip](#skip)
- [iam_role](#iam_role)
- [iam_assume_role_duration](#iam_assume_role_duration)
- [terraform_binary](#terraform_binary)
- [terraform_version_constraint](#terraform_version_constraint)
- [terragrunt_version_constraint](#terragrunt_version_constraint)
Expand Down Expand Up @@ -725,6 +726,21 @@ iam_role = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"
```


### iam_assume_role_duration

The `iam_assume_role_duration` attribute can be used to specify the STS session duration, in seconds, for the IAM role that Terragrunt should assume prior to invoking Terraform.

The precedence is as follows: `--terragrunt-iam-assume-role-duration` command line option → `TERRAGRUNT_IAM_ASSUME_ROLE_DURATION` env variable →
`iam_assume_role_duration` attribute of the `terragrunt.hcl` file in the module directory → `iam_assume_role_duration` attribute of the included
`terragrunt.hcl`.

Example:

```hcl
iam_assume_role_duration = 14400
```


### terraform_binary

The terragrunt `terraform_binary` string option can be used to override the default terraform binary path (which is
Expand Down
7 changes: 7 additions & 0 deletions options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ const TerragruntCacheDir = ".terragrunt-cache"

const DefaultTFDataDir = ".terraform"

const DEFAULT_IAM_ASSUME_ROLE_DURATION = 3600

// TerragruntOptions represents options that configure the behavior of the Terragrunt program
type TerragruntOptions struct {
// Location of the Terragrunt config file
Expand Down Expand Up @@ -104,6 +106,9 @@ type TerragruntOptions struct {
// The ARN of an IAM Role to assume before running Terraform
IamRole string

// Duration of the STS Session
IamAssumeRoleDuration int64

// If set to true, continue running *-all commands even if a dependency has errors. This is mostly useful for 'output-all <some_variable>'. See https://github.com/gruntwork-io/terragrunt/issues/193
IgnoreDependencyErrors bool

Expand Down Expand Up @@ -197,6 +202,7 @@ func NewTerragruntOptions(terragruntConfigPath string) (*TerragruntOptions, erro
SourceMap: map[string]string{},
SourceUpdate: false,
DownloadDir: downloadDir,
IamAssumeRoleDuration: DEFAULT_IAM_ASSUME_ROLE_DURATION,
IgnoreDependencyErrors: false,
IgnoreDependencyOrder: false,
IgnoreExternalDependencies: false,
Expand Down Expand Up @@ -275,6 +281,7 @@ func (terragruntOptions *TerragruntOptions) Clone(terragruntConfigPath string) *
DownloadDir: terragruntOptions.DownloadDir,
Debug: terragruntOptions.Debug,
IamRole: terragruntOptions.IamRole,
IamAssumeRoleDuration: terragruntOptions.IamAssumeRoleDuration,
IgnoreDependencyErrors: terragruntOptions.IgnoreDependencyErrors,
IgnoreDependencyOrder: terragruntOptions.IgnoreDependencyOrder,
IgnoreExternalDependencies: terragruntOptions.IgnoreExternalDependencies,
Expand Down

0 comments on commit daabff2

Please sign in to comment.