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))
|
* 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))
|
* 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
|
## Previous Releases
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -165,46 +166,54 @@ func (b *Backend) ConfigSchema() *configschema.Block {
|
|||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "The role to be assumed",
|
Description: "The role to be assumed",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
"session_name": {
|
"session_name": {
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "The session name to use when assuming the role.",
|
Description: "The session name to use when assuming the role.",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
"external_id": {
|
"external_id": {
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "The external ID to use when assuming the role",
|
Description: "The external ID to use when assuming the role",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"assume_role_duration_seconds": {
|
"assume_role_duration_seconds": {
|
||||||
Type: cty.Number,
|
Type: cty.Number,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "Seconds to restrict the assume role session duration.",
|
Description: "Seconds to restrict the assume role session duration.",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"assume_role_policy": {
|
"assume_role_policy": {
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
|
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"assume_role_policy_arns": {
|
"assume_role_policy_arns": {
|
||||||
Type: cty.Set(cty.String),
|
Type: cty.Set(cty.String),
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
|
Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"assume_role_tags": {
|
"assume_role_tags": {
|
||||||
Type: cty.Map(cty.String),
|
Type: cty.Map(cty.String),
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "Assume role session tags.",
|
Description: "Assume role session tags.",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"assume_role_transitive_tag_keys": {
|
"assume_role_transitive_tag_keys": {
|
||||||
Type: cty.Set(cty.String),
|
Type: cty.Set(cty.String),
|
||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "Assume role session tag keys to pass to any subsequent sessions.",
|
Description: "Assume role session tag keys to pass to any subsequent sessions.",
|
||||||
|
Deprecated: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
"workspace_key_prefix": {
|
"workspace_key_prefix": {
|
||||||
@ -229,6 +238,65 @@ func (b *Backend) ConfigSchema() *configschema.Block {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Description: "Use the legacy authentication workflow, preferring environment variables over backend configuration.",
|
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))
|
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
|
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)
|
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 {
|
func configureAssumeRole(obj cty.Value) *awsbase.AssumeRole {
|
||||||
assumeRole := awsbase.AssumeRole{}
|
assumeRole := awsbase.AssumeRole{}
|
||||||
|
|
||||||
@ -518,36 +661,14 @@ func configureAssumeRole(obj cty.Value) *awsbase.AssumeRole {
|
|||||||
assumeRole.Policy = stringAttr(obj, "assume_role_policy")
|
assumeRole.Policy = stringAttr(obj, "assume_role_policy")
|
||||||
assumeRole.SessionName = stringAttr(obj, "session_name")
|
assumeRole.SessionName = stringAttr(obj, "session_name")
|
||||||
|
|
||||||
if value := obj.GetAttr("assume_role_policy_arns"); !value.IsNull() {
|
if val, ok := stringSliceAttrOk(obj, "assume_role_policy_arns"); ok {
|
||||||
value.ForEachElement(func(key, val cty.Value) (stop bool) {
|
assumeRole.PolicyARNs = val
|
||||||
v, ok := stringValueOk(val)
|
|
||||||
if ok {
|
|
||||||
assumeRole.PolicyARNs = append(assumeRole.PolicyARNs, v)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
if val, ok := stringMapAttrOk(obj, "assume_role_tags"); ok {
|
||||||
if tagMap := obj.GetAttr("assume_role_tags"); !tagMap.IsNull() {
|
assumeRole.Tags = val
|
||||||
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 := stringSliceAttrOk(obj, "assume_role_transitive_tag_keys"); ok {
|
||||||
if transitiveTagKeySet := obj.GetAttr("assume_role_transitive_tag_keys"); !transitiveTagKeySet.IsNull() {
|
assumeRole.TransitiveTagKeys = val
|
||||||
transitiveTagKeySet.ForEachElement(func(key, val cty.Value) (stop bool) {
|
|
||||||
v, ok := stringValueOk(val)
|
|
||||||
if ok {
|
|
||||||
assumeRole.TransitiveTagKeys = append(assumeRole.TransitiveTagKeys, v)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &assumeRole
|
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 {
|
func pathString(path cty.Path) string {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
for i, step := range path {
|
for i, step := range path {
|
||||||
@ -707,6 +841,35 @@ func pathString(path cty.Path) string {
|
|||||||
return buf.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.
|
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)
|
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"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws/arn"
|
"github.com/aws/aws-sdk-go-v2/aws/arn"
|
||||||
"github.com/opentofu/opentofu/internal/tfdiags"
|
"github.com/opentofu/opentofu/internal/tfdiags"
|
||||||
@ -63,6 +64,96 @@ func validateKMSKeyARN(path cty.Path, s string) (diags tfdiags.Diagnostics) {
|
|||||||
return diags
|
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 {
|
func isKeyARN(arn arn.ARN) bool {
|
||||||
return keyIdFromARNResource(arn.Resource) != "" || aliasIdFromARNResource(arn.Resource) != ""
|
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
|
#### 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.
|
* `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.
|
* `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.
|
* `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.
|
* `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.
|
* `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.
|
* `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.
|
* `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.
|
* `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
|
### S3 State Storage
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user