mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Backend/S3: Extract assume_role
as a separate block (#754)
Signed-off-by: tomasmik <tomasmik@protonmail.com>
This commit is contained in:
parent
c0b1e801f2
commit
080d89c9b6
@ -51,7 +51,8 @@ S3 BACKEND:
|
||||
|
||||
* The S3 backend was upgraded to use the V2 of the AWS SDK for Go ([#691](https://github.com/opentofu/opentofu/issues/691))
|
||||
* Adds support for `shared_config_files` and `shared_credentials_files` arguments and deprecates the `shared_credentials_file` argument. ([#690](https://github.com/opentofu/opentofu/issues/690))
|
||||
|
||||
* Arguments associated with assuming an IAM role were moved into a nested block - `assume_role`.
|
||||
This deprecates the arguments `role_arn`, `session_name`, `external_id`, `assume_role_duration_seconds`, `assume_role_policy`, `assume_role_policy_arns`, `assume_role_tags`, and `assume_role_transitive_tag_keys`. ([#747](https://github.com/opentofu/opentofu/issues/747))
|
||||
|
||||
## Previous Releases
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -165,46 +166,54 @@ func (b *Backend) ConfigSchema() *configschema.Block {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "The role to be assumed",
|
||||
Deprecated: true,
|
||||
},
|
||||
"session_name": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "The session name to use when assuming the role.",
|
||||
Deprecated: true,
|
||||
},
|
||||
"external_id": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "The external ID to use when assuming the role",
|
||||
Deprecated: true,
|
||||
},
|
||||
|
||||
"assume_role_duration_seconds": {
|
||||
Type: cty.Number,
|
||||
Optional: true,
|
||||
Description: "Seconds to restrict the assume role session duration.",
|
||||
Deprecated: true,
|
||||
},
|
||||
|
||||
"assume_role_policy": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
|
||||
Deprecated: true,
|
||||
},
|
||||
|
||||
"assume_role_policy_arns": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
|
||||
Deprecated: true,
|
||||
},
|
||||
|
||||
"assume_role_tags": {
|
||||
Type: cty.Map(cty.String),
|
||||
Optional: true,
|
||||
Description: "Assume role session tags.",
|
||||
Deprecated: true,
|
||||
},
|
||||
|
||||
"assume_role_transitive_tag_keys": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
Description: "Assume role session tag keys to pass to any subsequent sessions.",
|
||||
Deprecated: true,
|
||||
},
|
||||
|
||||
"workspace_key_prefix": {
|
||||
@ -229,6 +238,65 @@ func (b *Backend) ConfigSchema() *configschema.Block {
|
||||
Optional: true,
|
||||
Description: "Use the legacy authentication workflow, preferring environment variables over backend configuration.",
|
||||
},
|
||||
"assume_role": {
|
||||
NestedType: &configschema.Object{
|
||||
Nesting: configschema.NestingSingle,
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"role_arn": {
|
||||
Type: cty.String,
|
||||
Required: true,
|
||||
Description: "The role to be assumed.",
|
||||
},
|
||||
"duration": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "Seconds to restrict the assume role session duration.",
|
||||
},
|
||||
"external_id": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "The external ID to use when assuming the role",
|
||||
},
|
||||
"policy": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
|
||||
},
|
||||
"policy_arns": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
|
||||
},
|
||||
"session_name": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
Description: "The session name to use when assuming the role.",
|
||||
},
|
||||
"tags": {
|
||||
Type: cty.Map(cty.String),
|
||||
Optional: true,
|
||||
Description: "Assume role session tags.",
|
||||
},
|
||||
"transitive_tag_keys": {
|
||||
Type: cty.Set(cty.String),
|
||||
Optional: true,
|
||||
Description: "Assume role session tag keys to pass to any subsequent sessions.",
|
||||
},
|
||||
//
|
||||
// NOT SUPPORTED by `aws-sdk-go-base/v1`
|
||||
// Cannot be added yet.
|
||||
//
|
||||
// "source_identity": stringAttribute{
|
||||
// configschema.Attribute{
|
||||
// Type: cty.String,
|
||||
// Optional: true,
|
||||
// Description: "Source identity specified by the principal assuming the role.",
|
||||
// ValidateFunc: validAssumeRoleSourceIdentity,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -332,6 +400,39 @@ func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics)
|
||||
attrPath))
|
||||
}
|
||||
|
||||
var assumeRoleDeprecatedFields = map[string]string{
|
||||
"role_arn": "assume_role.role_arn",
|
||||
"session_name": "assume_role.session_name",
|
||||
"external_id": "assume_role.external_id",
|
||||
"assume_role_duration_seconds": "assume_role.duration",
|
||||
"assume_role_policy": "assume_role.policy",
|
||||
"assume_role_policy_arns": "assume_role.policy_arns",
|
||||
"assume_role_tags": "assume_role.tags",
|
||||
"assume_role_transitive_tag_keys": "assume_role.transitive_tag_keys",
|
||||
}
|
||||
|
||||
if val := obj.GetAttr("assume_role"); !val.IsNull() {
|
||||
diags = diags.Append(validateNestedAssumeRole(val, cty.Path{cty.GetAttrStep{Name: "assume_role"}}))
|
||||
|
||||
if defined := findDeprecatedFields(obj, assumeRoleDeprecatedFields); len(defined) != 0 {
|
||||
diags = diags.Append(tfdiags.WholeContainingBody(
|
||||
tfdiags.Error,
|
||||
"Conflicting Parameters",
|
||||
`The following deprecated parameters conflict with the parameter "assume_role". Replace them as follows:`+"\n"+
|
||||
formatDeprecated(defined),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
if defined := findDeprecatedFields(obj, assumeRoleDeprecatedFields); len(defined) != 0 {
|
||||
diags = diags.Append(tfdiags.WholeContainingBody(
|
||||
tfdiags.Warning,
|
||||
"Deprecated Parameters",
|
||||
`The following parameters have been deprecated. Replace them as follows:`+"\n"+
|
||||
formatDeprecated(defined),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
return obj, diags
|
||||
}
|
||||
|
||||
@ -455,7 +556,9 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
|
||||
}
|
||||
}
|
||||
|
||||
if value := obj.GetAttr("role_arn"); !value.IsNull() {
|
||||
if value := obj.GetAttr("assume_role"); !value.IsNull() {
|
||||
cfg.AssumeRole = configureNestedAssumeRole(obj)
|
||||
} else if value := obj.GetAttr("role_arn"); !value.IsNull() {
|
||||
cfg.AssumeRole = configureAssumeRole(obj)
|
||||
}
|
||||
|
||||
@ -509,6 +612,46 @@ func getS3Config(obj cty.Value) func(options *s3.Options) {
|
||||
}
|
||||
}
|
||||
|
||||
func configureNestedAssumeRole(obj cty.Value) *awsbase.AssumeRole {
|
||||
assumeRole := awsbase.AssumeRole{}
|
||||
|
||||
obj = obj.GetAttr("assume_role")
|
||||
if val, ok := stringAttrOk(obj, "role_arn"); ok {
|
||||
assumeRole.RoleARN = val
|
||||
}
|
||||
if val, ok := stringAttrOk(obj, "duration"); ok {
|
||||
dur, err := time.ParseDuration(val)
|
||||
if err != nil {
|
||||
// This should never happen because the schema should have
|
||||
// already validated the duration.
|
||||
panic(fmt.Sprintf("invalid duration %q: %s", val, err))
|
||||
}
|
||||
|
||||
assumeRole.Duration = dur
|
||||
}
|
||||
if val, ok := stringAttrOk(obj, "external_id"); ok {
|
||||
assumeRole.ExternalID = val
|
||||
}
|
||||
|
||||
if val, ok := stringAttrOk(obj, "policy"); ok {
|
||||
assumeRole.Policy = strings.TrimSpace(val)
|
||||
}
|
||||
if val, ok := stringSliceAttrOk(obj, "policy_arns"); ok {
|
||||
assumeRole.PolicyARNs = val
|
||||
}
|
||||
if val, ok := stringAttrOk(obj, "session_name"); ok {
|
||||
assumeRole.SessionName = val
|
||||
}
|
||||
if val, ok := stringMapAttrOk(obj, "tags"); ok {
|
||||
assumeRole.Tags = val
|
||||
}
|
||||
if val, ok := stringSliceAttrOk(obj, "transitive_tag_keys"); ok {
|
||||
assumeRole.TransitiveTagKeys = val
|
||||
}
|
||||
|
||||
return &assumeRole
|
||||
}
|
||||
|
||||
func configureAssumeRole(obj cty.Value) *awsbase.AssumeRole {
|
||||
assumeRole := awsbase.AssumeRole{}
|
||||
|
||||
@ -518,36 +661,14 @@ func configureAssumeRole(obj cty.Value) *awsbase.AssumeRole {
|
||||
assumeRole.Policy = stringAttr(obj, "assume_role_policy")
|
||||
assumeRole.SessionName = stringAttr(obj, "session_name")
|
||||
|
||||
if value := obj.GetAttr("assume_role_policy_arns"); !value.IsNull() {
|
||||
value.ForEachElement(func(key, val cty.Value) (stop bool) {
|
||||
v, ok := stringValueOk(val)
|
||||
if ok {
|
||||
assumeRole.PolicyARNs = append(assumeRole.PolicyARNs, v)
|
||||
}
|
||||
return
|
||||
})
|
||||
if val, ok := stringSliceAttrOk(obj, "assume_role_policy_arns"); ok {
|
||||
assumeRole.PolicyARNs = val
|
||||
}
|
||||
|
||||
if tagMap := obj.GetAttr("assume_role_tags"); !tagMap.IsNull() {
|
||||
assumeRole.Tags = make(map[string]string, tagMap.LengthInt())
|
||||
tagMap.ForEachElement(func(key, val cty.Value) (stop bool) {
|
||||
k := stringValue(key)
|
||||
v, ok := stringValueOk(val)
|
||||
if ok {
|
||||
assumeRole.Tags[k] = v
|
||||
}
|
||||
return
|
||||
})
|
||||
if val, ok := stringMapAttrOk(obj, "assume_role_tags"); ok {
|
||||
assumeRole.Tags = val
|
||||
}
|
||||
|
||||
if transitiveTagKeySet := obj.GetAttr("assume_role_transitive_tag_keys"); !transitiveTagKeySet.IsNull() {
|
||||
transitiveTagKeySet.ForEachElement(func(key, val cty.Value) (stop bool) {
|
||||
v, ok := stringValueOk(val)
|
||||
if ok {
|
||||
assumeRole.TransitiveTagKeys = append(assumeRole.TransitiveTagKeys, v)
|
||||
}
|
||||
return
|
||||
})
|
||||
if val, ok := stringSliceAttrOk(obj, "assume_role_transitive_tag_keys"); ok {
|
||||
assumeRole.TransitiveTagKeys = val
|
||||
}
|
||||
|
||||
return &assumeRole
|
||||
@ -670,6 +791,19 @@ func intAttrDefault(obj cty.Value, name string, def int) int {
|
||||
}
|
||||
}
|
||||
|
||||
func stringMapValueOk(val cty.Value) (map[string]string, bool) {
|
||||
var m map[string]string
|
||||
err := gocty.FromCtyValue(val, &m)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
return m, true
|
||||
}
|
||||
|
||||
func stringMapAttrOk(obj cty.Value, name string) (map[string]string, bool) {
|
||||
return stringMapValueOk(obj.GetAttr(name))
|
||||
}
|
||||
|
||||
func pathString(path cty.Path) string {
|
||||
var buf strings.Builder
|
||||
for i, step := range path {
|
||||
@ -707,6 +841,35 @@ func pathString(path cty.Path) string {
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func findDeprecatedFields(obj cty.Value, attrs map[string]string) map[string]string {
|
||||
defined := make(map[string]string)
|
||||
for attr, v := range attrs {
|
||||
if val := obj.GetAttr(attr); !val.IsNull() {
|
||||
defined[attr] = v
|
||||
}
|
||||
}
|
||||
return defined
|
||||
}
|
||||
|
||||
func formatDeprecated(attrs map[string]string) string {
|
||||
var maxLen int
|
||||
var buf strings.Builder
|
||||
|
||||
names := make([]string, 0, len(attrs))
|
||||
for deprecated, replacement := range attrs {
|
||||
names = append(names, deprecated)
|
||||
if l := len(deprecated); l > maxLen {
|
||||
maxLen = l
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, " * %-[1]*[2]s -> %s\n", maxLen, deprecated, replacement)
|
||||
}
|
||||
|
||||
sort.Strings(names)
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
const encryptionKeyConflictError = `Only one of "kms_key_id" and "sse_customer_key" can be set.
|
||||
|
||||
The "kms_key_id" is used for encryption with KMS-Managed Keys (SSE-KMS)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go-v2/aws/arn"
|
||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||
@ -63,6 +64,96 @@ func validateKMSKeyARN(path cty.Path, s string) (diags tfdiags.Diagnostics) {
|
||||
return diags
|
||||
}
|
||||
|
||||
func validateNestedAssumeRole(obj cty.Value, objPath cty.Path) tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
if val, ok := stringAttrOk(obj, "role_arn"); !ok || val == "" {
|
||||
path := objPath.GetAttr("role_arn")
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Missing Required Value",
|
||||
fmt.Sprintf("The attribute %q is required by the backend.\n\n", pathString(path))+
|
||||
"Refer to the backend documentation for additional information which attributes are required.",
|
||||
path,
|
||||
))
|
||||
}
|
||||
|
||||
if val, ok := stringAttrOk(obj, "duration"); ok {
|
||||
path := objPath.GetAttr("duration")
|
||||
d, err := time.ParseDuration(val)
|
||||
if err != nil {
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Invalid Duration",
|
||||
fmt.Sprintf("The value %q cannot be parsed as a duration: %s", val, err),
|
||||
path,
|
||||
))
|
||||
} else {
|
||||
min := 15 * time.Minute
|
||||
max := 12 * time.Hour
|
||||
if d < min || d > max {
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Invalid Duration",
|
||||
fmt.Sprintf("Duration must be between %s and %s, had %s", min, max, val),
|
||||
path,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := stringAttrOk(obj, "external_id"); ok {
|
||||
if len(strings.TrimSpace(val)) == 0 {
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Invalid Value",
|
||||
"The value cannot be empty or all whitespace",
|
||||
objPath.GetAttr("external_id"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := stringAttrOk(obj, "policy"); ok {
|
||||
if len(strings.TrimSpace(val)) == 0 {
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Invalid Value",
|
||||
"The value cannot be empty or all whitespace",
|
||||
objPath.GetAttr("policy"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := stringAttrOk(obj, "session_name"); ok {
|
||||
if len(strings.TrimSpace(val)) == 0 {
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Invalid Value",
|
||||
"The value cannot be empty or all whitespace",
|
||||
objPath.GetAttr("session_name"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := stringSliceAttrOk(obj, "policy_arns"); ok {
|
||||
for _, v := range val {
|
||||
arn, err := arn.Parse(v)
|
||||
if err != nil {
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Invalid ARN",
|
||||
fmt.Sprintf("The value %q cannot be parsed as an ARN: %s", val, err),
|
||||
objPath.GetAttr("policy_arns"),
|
||||
))
|
||||
break
|
||||
} else {
|
||||
if !strings.HasPrefix(arn.Resource, "policy/") {
|
||||
diags = diags.Append(attributeErrDiag(
|
||||
"Invalid IAM Policy ARN",
|
||||
fmt.Sprintf("Value must be a valid IAM Policy ARN, got %q", val),
|
||||
objPath.GetAttr("policy_arns"),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func isKeyARN(arn arn.ARN) bool {
|
||||
return keyIdFromARNResource(arn.Resource) != "" || aliasIdFromARNResource(arn.Resource) != ""
|
||||
}
|
||||
|
@ -188,3 +188,150 @@ func Test_validateAttributesConflict(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validateNestedAssumeRole(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
input cty.Value
|
||||
expectedDiags []string
|
||||
}{
|
||||
{
|
||||
description: "Valid Input",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("30m"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:policy/valid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: nil,
|
||||
},
|
||||
{
|
||||
description: "Missing Role ARN",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal(""),
|
||||
"duration": cty.StringVal("30m"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:policy/valid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"The attribute \"assume_role.role_arn\" is required by the backend.\n\nRefer to the backend documentation for additional information which attributes are required.",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid Duration",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("invalid-duration"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:policy/valid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"The value \"invalid-duration\" cannot be parsed as a duration: time: invalid duration \"invalid-duration\"",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid Duration Length",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("44h"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:policy/valid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"Duration must be between 15m0s and 12h0m0s, had 44h",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid External ID (Empty)",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("30m"),
|
||||
"external_id": cty.StringVal(""),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:policy/valid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"The value cannot be empty or all whitespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid Policy (Empty)",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("30m"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal(""),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:policy/valid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"The value cannot be empty or all whitespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid Session Name (Empty)",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("30m"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal(""),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:policy/valid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"The value cannot be empty or all whitespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid Policy ARN (Invalid ARN Format)",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("30m"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("invalid-arn-format")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"The value [\"invalid-arn-format\"] cannot be parsed as an ARN: arn: invalid prefix",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Invalid Policy ARN (Not Starting with 'policy/')",
|
||||
input: cty.ObjectVal(map[string]cty.Value{
|
||||
"role_arn": cty.StringVal("valid-role-arn"),
|
||||
"duration": cty.StringVal("30m"),
|
||||
"external_id": cty.StringVal("valid-external-id"),
|
||||
"policy": cty.StringVal("valid-policy"),
|
||||
"session_name": cty.StringVal("valid-session-name"),
|
||||
"policy_arns": cty.ListVal([]cty.Value{cty.StringVal("arn:aws:iam::123456789012:role/invalid-policy-arn")}),
|
||||
}),
|
||||
expectedDiags: []string{
|
||||
"Value must be a valid IAM Policy ARN, got [\"arn:aws:iam::123456789012:role/invalid-policy-arn\"]",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
diagnostics := validateNestedAssumeRole(test.input, cty.Path{cty.GetAttrStep{Name: "assume_role"}})
|
||||
if len(diagnostics) != len(test.expectedDiags) {
|
||||
t.Errorf("Expected %d diagnostics, but got %d", len(test.expectedDiags), len(diagnostics))
|
||||
}
|
||||
for i, diag := range diagnostics {
|
||||
if diag.Description().Detail != test.expectedDiags[i] {
|
||||
t.Errorf("Mismatch in diagnostic %d. Expected: %q, Got: %q", i, test.expectedDiags[i], diag.Description().Detail)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -174,16 +174,55 @@ The following configuration is optional:
|
||||
|
||||
#### Assume Role Configuration
|
||||
|
||||
The following configuration is optional:
|
||||
Assuming an IAM Role is optional and can be configured in two ways.
|
||||
The preferred way is to use the argument `assume_role`, as the other, the other method is deprecated.
|
||||
|
||||
The argument `assume_role` contains the following arguments:
|
||||
|
||||
* `role_arn` - (Required) The Amazon Resource Name (ARN) of the IAM Role to be assumed.
|
||||
* `duration` - (Optional) Specifies the validity period for individual credentials.
|
||||
These credentials are automatically renewed, with the maximum renewal defined by the AWS account.
|
||||
The duration should be specified in the format `<hours>h<minutes>m<seconds>s`, with each unit being optional.
|
||||
For example, an hour and a half can be represented as `1h30m` or simply `90m`.
|
||||
The duration must be within the range of 15 minutes (15m) to 12 hours (12h).
|
||||
* `external_id` - (Optional) An external identifier to use when assuming the role.
|
||||
* `policy` - (Optional) JSON representation of an IAM Policy that further restricts permissions for the IAM Role being assumed.
|
||||
* `policy_arns` - (Optional) A set of Amazon Resource Names (ARNs) for IAM Policies that further limit permissions for the assumed IAM Role.
|
||||
* `session_name` - (Optional) The session name to be used when assuming the role.
|
||||
* `tags` - (Optional) A map of tags to be associated with the assumed role session.
|
||||
* `transitive_tag_keys` - (Optional) A set of tag keys from the assumed role session to be passed to any subsequent sessions.
|
||||
|
||||
The following arguments on the top level are deprecated:
|
||||
|
||||
* `assume_role_duration_seconds` - (Optional) Number of seconds to restrict the assume role session duration.
|
||||
Use `assume_role.duration` instead.
|
||||
* `assume_role_policy` - (Optional) IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.
|
||||
Use `assume_role.policy` instead.
|
||||
* `assume_role_policy_arns` - (Optional) Set of Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.
|
||||
Use `assume_role.policy_arns` instead.
|
||||
* `assume_role_tags` - (Optional) Map of assume role session tags.
|
||||
Use `assume_role.tags` instead.
|
||||
* `assume_role_transitive_tag_keys` - (Optional) Set of assume role session tag keys to pass to any subsequent sessions.
|
||||
Use `assume_role.transitive_tag_keys` instead.
|
||||
* `external_id` - (Optional) External identifier to use when assuming the role.
|
||||
Use `assume_role.external_id` instead.
|
||||
* `role_arn` - (Optional) Amazon Resource Name (ARN) of the IAM Role to assume.
|
||||
Use `assume_role.role_arn` instead.
|
||||
* `session_name` - (Optional) Session name to use when assuming the role.
|
||||
Use `assume_role.session_name` instead.
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "mybucket"
|
||||
key = "my/key.tfstate"
|
||||
region = "us-east-1"
|
||||
assume_role = {
|
||||
role_arn = "arn:aws:iam::ACCOUNT-ID:role/Opentofu"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### S3 State Storage
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user