Merge pull request #33872 from hashicorp/s3/f-schema-single-nested-object

backend/s3: Adds `singleNestedAttribute` to internal schema
pull/33873/merge
Graham Davison 3 years ago committed by GitHub
commit 9437c43540
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -94,33 +94,9 @@ func (b *Backend) ConfigSchema() *configschema.Block {
Description: "A custom endpoint for the S3 API",
Deprecated: true,
},
"endpoints": {
NestedType: &configschema.Object{
Nesting: configschema.NestingSingle,
Attributes: map[string]*configschema.Attribute{
"dynamodb": {
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the DynamoDB API",
},
"iam": {
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the IAM API",
},
"s3": {
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the S3 API",
},
"sts": {
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the STS API",
},
},
},
},
"endpoints": endpointsSchema.SchemaAttribute(),
"forbidden_account_ids": {
Type: cty.Set(cty.String),
Optional: true,
@ -304,19 +280,9 @@ func (b *Backend) ConfigSchema() *configschema.Block {
Description: "The maximum number of times an AWS API request is retried on retryable failure.",
},
"assume_role": {
NestedType: &configschema.Object{
Nesting: configschema.NestingSingle,
Attributes: assumeRoleFullSchema().SchemaAttributes(),
},
},
"assume_role": assumeRoleSchema.SchemaAttribute(),
"assume_role_with_web_identity": {
NestedType: &configschema.Object{
Nesting: configschema.NestingSingle,
Attributes: assumeRoleWithWebIdentityFullSchema().SchemaAttributes(),
},
},
"assume_role_with_web_identity": assumeRoleWithWebIdentitySchema.SchemaAttribute(),
"use_legacy_workflow": {
Type: cty.Bool,
@ -354,132 +320,431 @@ func (b *Backend) ConfigSchema() *configschema.Block {
}
}
// PrepareConfig checks the validity of the values in the given
// configuration, and inserts any missing defaults, assuming that its
// structure has already been validated per the schema returned by
// ConfigSchema.
func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
if obj.IsNull() {
return obj, diags
}
var attrPath cty.Path
attrPath = cty.GetAttrPath("bucket")
if val := obj.GetAttr("bucket"); val.IsNull() {
diags = diags.Append(requiredAttributeErrDiag(attrPath))
} else {
bucketValidators := validateString{
Validators: []stringValidator{
validateStringNotEmpty,
var assumeRoleSchema = singleNestedAttribute{
Attributes: map[string]schemaAttribute{
"role_arn": stringAttribute{
configschema.Attribute{
Type: cty.String,
Required: true,
Description: "The role to be assumed.",
},
}
bucketValidators.ValidateAttr(val, attrPath, &diags)
}
attrPath = cty.GetAttrPath("key")
if val := obj.GetAttr("key"); val.IsNull() {
diags = diags.Append(requiredAttributeErrDiag(attrPath))
} else {
keyValidators := validateString{
Validators: []stringValidator{
validateStringNotEmpty,
validateStringS3Path,
validateStringDoesNotContain("//"),
validateString{
Validators: []stringValidator{
validateARN(
validateIAMRoleARN,
),
},
},
}
keyValidators.ValidateAttr(val, attrPath, &diags)
}
},
// Not updating region handling, because validation will be handled by `aws-sdk-go-base` once it is updated
if val := obj.GetAttr("region"); val.IsNull() || val.AsString() == "" {
if os.Getenv("AWS_REGION") == "" && os.Getenv("AWS_DEFAULT_REGION") == "" {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Missing region value",
`The "region" attribute or the "AWS_REGION" or "AWS_DEFAULT_REGION" environment variables must be set.`,
cty.GetAttrPath("region"),
))
}
}
"duration": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.",
},
validateString{
Validators: []stringValidator{
validateDuration(
validateDurationBetween(15*time.Minute, 12*time.Hour),
),
},
},
},
validateAttributesConflict(
cty.GetAttrPath("kms_key_id"),
cty.GetAttrPath("sse_customer_key"),
)(obj, cty.Path{}, &diags)
"external_id": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The external ID to use when assuming the role",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(2, 1224),
validateStringMatches(
regexp.MustCompile(`^[\w+=,.@:\/\-]*$`),
`Value can only contain letters, numbers, or the following characters: =,.@/-`,
),
},
},
},
attrPath = cty.GetAttrPath("kms_key_id")
if val := obj.GetAttr("kms_key_id"); !val.IsNull() {
kmsKeyIDValidators := validateString{
Validators: []stringValidator{
validateStringKMSKey,
"policy": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
},
}
kmsKeyIDValidators.ValidateAttr(val, attrPath, &diags)
}
validateString{
Validators: []stringValidator{
validateStringNotEmpty,
validateIAMPolicyDocument,
},
},
},
attrPath = cty.GetAttrPath("workspace_key_prefix")
if val := obj.GetAttr("workspace_key_prefix"); !val.IsNull() {
keyPrefixValidators := validateString{
Validators: []stringValidator{
validateStringS3Path,
"policy_arns": setAttribute{
configschema.Attribute{
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.",
},
}
keyPrefixValidators.ValidateAttr(val, attrPath, &diags)
}
validateSet{
Validators: []setValidator{
validateSetStringElements(
validateARN(
validateIAMPolicyARN,
),
),
},
},
},
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",
}
"session_name": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The session name to use when assuming the role.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(2, 64),
validateStringMatches(
regexp.MustCompile(`^[\w+=,.@\-]*$`),
`Value can only contain letters, numbers, or the following characters: =,.@-`,
),
},
},
},
if val := obj.GetAttr("assume_role"); !val.IsNull() {
diags = diags.Append(prepareAssumeRoleConfig(val, cty.GetAttrPath("assume_role")))
// NOT SUPPORTED by `aws-sdk-go-base/v1`
// "source_identity": stringAttribute{
// configschema.Attribute{
// Type: cty.String,
// Optional: true,
// Description: "Source identity specified by the principal assuming the role.",
// ValidateFunc: validAssumeRoleSourceIdentity,
// },
// },
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"+
formatDeprecations(defined),
))
}
} else {
if defined := findDeprecatedFields(obj, assumeRoleDeprecatedFields); len(defined) != 0 {
diags = diags.Append(wholeBodyWarningDiag(
"Deprecated Parameters",
`The following parameters have been deprecated. Replace them as follows:`+"\n"+
formatDeprecations(defined),
))
}
}
"tags": mapAttribute{
configschema.Attribute{
Type: cty.Map(cty.String),
Optional: true,
Description: "Assume role session tags.",
},
validateMap{},
},
if val := obj.GetAttr("assume_role_with_web_identity"); !val.IsNull() {
diags = diags.Append(prepareAssumeRoleWithWebIdentityConfig(val, cty.GetAttrPath("assume_role_with_web_identity")))
}
"transitive_tag_keys": setAttribute{
configschema.Attribute{
Type: cty.Set(cty.String),
Optional: true,
Description: "Assume role session tag keys to pass to any subsequent sessions.",
},
validateSet{},
},
},
}
validateAttributesConflict(
cty.GetAttrPath("shared_credentials_file"),
cty.GetAttrPath("shared_credentials_files"),
)(obj, cty.Path{}, &diags)
var assumeRoleWithWebIdentitySchema = singleNestedAttribute{
Attributes: map[string]schemaAttribute{
"role_arn": stringAttribute{
configschema.Attribute{
Type: cty.String,
Required: true,
Description: "The role to be assumed.",
},
validateString{
Validators: []stringValidator{
validateARN(
validateIAMRoleARN,
),
},
},
},
attrPath = cty.GetAttrPath("shared_credentials_file")
if val := obj.GetAttr("shared_credentials_file"); !val.IsNull() {
diags = diags.Append(deprecatedAttrDiag(attrPath, cty.GetAttrPath("shared_credentials_files")))
}
"duration": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.",
},
validateString{
Validators: []stringValidator{
validateDuration(
validateDurationBetween(15*time.Minute, 12*time.Hour),
),
},
},
},
endpointFields := map[string]string{
"dynamodb_endpoint": "dynamodb",
"iam_endpoint": "iam",
"endpoint": "s3",
"sts_endpoint": "sts",
"policy": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
},
validateString{
Validators: []stringValidator{
validateStringNotEmpty,
validateIAMPolicyDocument,
},
},
},
"policy_arns": setAttribute{
configschema.Attribute{
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.",
},
validateSet{
Validators: []setValidator{
validateSetStringElements(
validateARN(
validateIAMPolicyARN,
),
),
},
},
},
"session_name": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The session name to use when assuming the role.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(2, 64),
validateStringMatches(
regexp.MustCompile(`^[\w+=,.@\-]*$`),
`Value can only contain letters, numbers, or the following characters: =,.@-`,
),
},
},
},
"web_identity_token": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "Value of a web identity token from an OpenID Connect (OIDC) or OAuth provider.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(4, 20000),
},
},
},
"web_identity_token_file": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "File containing a web identity token from an OpenID Connect (OIDC) or OAuth provider.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(4, 20000),
},
},
},
},
validateObject: validateObject{
Validators: []objectValidator{
validateExactlyOneOfAttributes(
cty.GetAttrPath("web_identity_token"),
cty.GetAttrPath("web_identity_token_file"),
),
},
},
}
var endpointsSchema = singleNestedAttribute{
Attributes: map[string]schemaAttribute{
"dynamodb": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the DynamoDB API",
},
validateString{
Validators: []stringValidator{
validateStringURL,
},
},
},
"iam": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the IAM API",
},
validateString{
Validators: []stringValidator{
validateStringURL,
},
},
},
"s3": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the S3 API",
},
validateString{
Validators: []stringValidator{
validateStringURL,
},
},
},
"sts": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "A custom endpoint for the STS API",
},
validateString{
Validators: []stringValidator{
validateStringURL,
},
},
},
},
}
// PrepareConfig checks the validity of the values in the given
// configuration, and inserts any missing defaults, assuming that its
// structure has already been validated per the schema returned by
// ConfigSchema.
func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
if obj.IsNull() {
return obj, diags
}
var attrPath cty.Path
attrPath = cty.GetAttrPath("bucket")
if val := obj.GetAttr("bucket"); val.IsNull() {
diags = diags.Append(requiredAttributeErrDiag(attrPath))
} else {
bucketValidators := validateString{
Validators: []stringValidator{
validateStringNotEmpty,
},
}
bucketValidators.ValidateAttr(val, attrPath, &diags)
}
attrPath = cty.GetAttrPath("key")
if val := obj.GetAttr("key"); val.IsNull() {
diags = diags.Append(requiredAttributeErrDiag(attrPath))
} else {
keyValidators := validateString{
Validators: []stringValidator{
validateStringNotEmpty,
validateStringS3Path,
validateStringDoesNotContain("//"),
},
}
keyValidators.ValidateAttr(val, attrPath, &diags)
}
// Not updating region handling, because validation will be handled by `aws-sdk-go-base` once it is updated
if val := obj.GetAttr("region"); val.IsNull() || val.AsString() == "" {
if os.Getenv("AWS_REGION") == "" && os.Getenv("AWS_DEFAULT_REGION") == "" {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Missing region value",
`The "region" attribute or the "AWS_REGION" or "AWS_DEFAULT_REGION" environment variables must be set.`,
cty.GetAttrPath("region"),
))
}
}
validateAttributesConflict(
cty.GetAttrPath("kms_key_id"),
cty.GetAttrPath("sse_customer_key"),
)(obj, cty.Path{}, &diags)
attrPath = cty.GetAttrPath("kms_key_id")
if val := obj.GetAttr("kms_key_id"); !val.IsNull() {
kmsKeyIDValidators := validateString{
Validators: []stringValidator{
validateStringKMSKey,
},
}
kmsKeyIDValidators.ValidateAttr(val, attrPath, &diags)
}
attrPath = cty.GetAttrPath("workspace_key_prefix")
if val := obj.GetAttr("workspace_key_prefix"); !val.IsNull() {
keyPrefixValidators := validateString{
Validators: []stringValidator{
validateStringS3Path,
},
}
keyPrefixValidators.ValidateAttr(val, attrPath, &diags)
}
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() {
validateNestedAttribute(assumeRoleSchema, val, cty.GetAttrPath("assume_role"), &diags)
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"+
formatDeprecations(defined),
))
}
} else {
if defined := findDeprecatedFields(obj, assumeRoleDeprecatedFields); len(defined) != 0 {
diags = diags.Append(wholeBodyWarningDiag(
"Deprecated Parameters",
`The following parameters have been deprecated. Replace them as follows:`+"\n"+
formatDeprecations(defined),
))
}
}
if val := obj.GetAttr("assume_role_with_web_identity"); !val.IsNull() {
validateNestedAttribute(assumeRoleWithWebIdentitySchema, val, cty.GetAttrPath("assume_role_with_web_identity"), &diags)
}
validateAttributesConflict(
cty.GetAttrPath("shared_credentials_file"),
cty.GetAttrPath("shared_credentials_files"),
)(obj, cty.Path{}, &diags)
attrPath = cty.GetAttrPath("shared_credentials_file")
if val := obj.GetAttr("shared_credentials_file"); !val.IsNull() {
diags = diags.Append(deprecatedAttrDiag(attrPath, cty.GetAttrPath("shared_credentials_files")))
}
endpointFields := map[string]string{
"dynamodb_endpoint": "dynamodb",
"iam_endpoint": "iam",
"endpoint": "s3",
"sts_endpoint": "sts",
}
endpoints := make(map[string]string)
if val := obj.GetAttr("endpoints"); !val.IsNull() {
@ -504,20 +769,15 @@ func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics)
}
}
if val := obj.GetAttr("endpoints"); !val.IsNull() {
validateNestedAttribute(endpointsSchema, val, cty.GetAttrPath("endpoints"), &diags)
}
endpointValidators := validateString{
Validators: []stringValidator{
validateStringURL,
},
}
if val := obj.GetAttr("endpoints"); !val.IsNull() {
attrPath := cty.GetAttrPath("endpoints")
for _, k := range []string{"dynamodb", "iam", "s3", "sts"} {
if v := val.GetAttr(k); !v.IsNull() {
attrPath := attrPath.GetAttr(k)
endpointValidators.ValidateAttr(v, attrPath, &diags)
}
}
}
for _, k := range maps.Keys(endpointFields) {
if val := obj.GetAttr(k); !val.IsNull() {
attrPath := cty.GetAttrPath(k)
@ -1197,50 +1457,25 @@ The "kms_key_id" is used for encryption with KMS-Managed Keys (SSE-KMS)
while "AWS_SSE_CUSTOMER_KEY" is used for encryption with customer-managed keys (SSE-C).
Please choose one or the other.`
func prepareAssumeRoleConfig(obj cty.Value, objPath cty.Path) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
func validateNestedAttribute(objSchema schemaAttribute, obj cty.Value, objPath cty.Path, diags *tfdiags.Diagnostics) {
if obj.IsNull() {
return diags
return
}
for name, attrSchema := range assumeRoleFullSchema() {
attrPath := objPath.GetAttr(name)
attrVal := obj.GetAttr(name)
if a, e := attrVal.Type(), attrSchema.SchemaAttribute().Type; a != e {
diags = diags.Append(attributeErrDiag(
"Internal Error",
fmt.Sprintf(`Expected type to be %s, got: %s`, e.FriendlyName(), a.FriendlyName()),
attrPath,
))
continue
}
if attrVal.IsNull() {
if attrSchema.SchemaAttribute().Required {
diags = diags.Append(requiredAttributeErrDiag(attrPath))
}
continue
}
validator := attrSchema.Validator()
validator.ValidateAttr(attrVal, attrPath, &diags)
na, ok := objSchema.(singleNestedAttribute)
if !ok {
return
}
return diags
}
func prepareAssumeRoleWithWebIdentityConfig(obj cty.Value, objPath cty.Path) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
if obj.IsNull() {
return diags
}
validator := objSchema.Validator()
validator.ValidateAttr(obj, objPath, diags)
for name, attrSchema := range assumeRoleWithWebIdentityFullSchema() {
for name, attrSchema := range na.Attributes {
attrPath := objPath.GetAttr(name)
attrVal := obj.GetAttr(name)
if a, e := attrVal.Type(), attrSchema.SchemaAttribute().Type; a != e {
diags = diags.Append(attributeErrDiag(
*diags = diags.Append(attributeErrDiag(
"Internal Error",
fmt.Sprintf(`Expected type to be %s, got: %s`, e.FriendlyName(), a.FriendlyName()),
attrPath,
@ -1250,21 +1485,14 @@ func prepareAssumeRoleWithWebIdentityConfig(obj cty.Value, objPath cty.Path) tfd
if attrVal.IsNull() {
if attrSchema.SchemaAttribute().Required {
diags = diags.Append(requiredAttributeErrDiag(attrPath))
*diags = diags.Append(requiredAttributeErrDiag(attrPath))
}
continue
}
validator := attrSchema.Validator()
validator.ValidateAttr(attrVal, attrPath, &diags)
validator.ValidateAttr(attrVal, attrPath, diags)
}
validateExactlyOneOfAttributes(
cty.GetAttrPath("web_identity_token"),
cty.GetAttrPath("web_identity_token_file"),
)(obj, objPath, &diags)
return diags
}
func requiredAttributeErrDiag(path cty.Path) tfdiags.Diagnostic {
@ -1344,11 +1572,23 @@ func (v validateSet) ValidateAttr(val cty.Value, attrPath cty.Path, diags *tfdia
}
}
type validateObject struct {
Validators []objectValidator
}
func (v validateObject) ValidateAttr(val cty.Value, attrPath cty.Path, diags *tfdiags.Diagnostics) {
for _, validator := range v.Validators {
validator(val, attrPath, diags)
}
}
type schemaAttribute interface {
SchemaAttribute() *configschema.Attribute
Validator() validateSchema
}
var _ schemaAttribute = stringAttribute{}
type stringAttribute struct {
configschema.Attribute
validateString
@ -1362,6 +1602,8 @@ func (a stringAttribute) Validator() validateSchema {
return a.validateString
}
var _ schemaAttribute = setAttribute{}
type setAttribute struct {
configschema.Attribute
validateSet
@ -1375,6 +1617,8 @@ func (a setAttribute) Validator() validateSchema {
return a.validateSet
}
var _ schemaAttribute = mapAttribute{}
type mapAttribute struct {
configschema.Attribute
validateMap
@ -1398,239 +1642,24 @@ func (s objectSchema) SchemaAttributes() map[string]*configschema.Attribute {
return m
}
func assumeRoleFullSchema() objectSchema {
return map[string]schemaAttribute{
"role_arn": stringAttribute{
configschema.Attribute{
Type: cty.String,
Required: true,
Description: "The role to be assumed.",
},
validateString{
Validators: []stringValidator{
validateARN(
validateIAMRoleARN,
),
},
},
},
"duration": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.",
},
validateString{
Validators: []stringValidator{
validateDuration(
validateDurationBetween(15*time.Minute, 12*time.Hour),
),
},
},
},
"external_id": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The external ID to use when assuming the role",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(2, 1224),
validateStringMatches(
regexp.MustCompile(`^[\w+=,.@:\/\-]*$`),
`Value can only contain letters, numbers, or the following characters: =,.@/-`,
),
},
},
},
"policy": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
},
validateString{
Validators: []stringValidator{
validateStringNotEmpty,
validateIAMPolicyDocument,
},
},
},
"policy_arns": setAttribute{
configschema.Attribute{
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.",
},
validateSet{
Validators: []setValidator{
validateSetStringElements(
validateARN(
validateIAMPolicyARN,
),
),
},
},
},
"session_name": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The session name to use when assuming the role.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(2, 64),
validateStringMatches(
regexp.MustCompile(`^[\w+=,.@\-]*$`),
`Value can only contain letters, numbers, or the following characters: =,.@-`,
),
},
},
},
var _ schemaAttribute = singleNestedAttribute{}
// NOT SUPPORTED by `aws-sdk-go-base/v1`
// "source_identity": stringAttribute{
// configschema.Attribute{
// Type: cty.String,
// Optional: true,
// Description: "Source identity specified by the principal assuming the role.",
// ValidateFunc: validAssumeRoleSourceIdentity,
// },
// },
"tags": mapAttribute{
configschema.Attribute{
Type: cty.Map(cty.String),
Optional: true,
Description: "Assume role session tags.",
},
validateMap{},
},
type singleNestedAttribute struct {
Attributes objectSchema
validateObject
}
"transitive_tag_keys": setAttribute{
configschema.Attribute{
Type: cty.Set(cty.String),
Optional: true,
Description: "Assume role session tag keys to pass to any subsequent sessions.",
},
validateSet{},
func (a singleNestedAttribute) SchemaAttribute() *configschema.Attribute {
return &configschema.Attribute{
NestedType: &configschema.Object{
Nesting: configschema.NestingSingle,
Attributes: a.Attributes.SchemaAttributes(),
},
}
}
func assumeRoleWithWebIdentityFullSchema() objectSchema {
return map[string]schemaAttribute{
"role_arn": stringAttribute{
configschema.Attribute{
Type: cty.String,
Required: true,
Description: "The role to be assumed.",
},
validateString{
Validators: []stringValidator{
validateARN(
validateIAMRoleARN,
),
},
},
},
"duration": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.",
},
validateString{
Validators: []stringValidator{
validateDuration(
validateDurationBetween(15*time.Minute, 12*time.Hour),
),
},
},
},
"policy": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.",
},
validateString{
Validators: []stringValidator{
validateStringNotEmpty,
validateIAMPolicyDocument,
},
},
},
"policy_arns": setAttribute{
configschema.Attribute{
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.",
},
validateSet{
Validators: []setValidator{
validateSetStringElements(
validateARN(
validateIAMPolicyARN,
),
),
},
},
},
"session_name": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "The session name to use when assuming the role.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(2, 64),
validateStringMatches(
regexp.MustCompile(`^[\w+=,.@\-]*$`),
`Value can only contain letters, numbers, or the following characters: =,.@-`,
),
},
},
},
"web_identity_token": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "Value of a web identity token from an OpenID Connect (OIDC) or OAuth provider.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(4, 20000),
},
},
},
"web_identity_token_file": stringAttribute{
configschema.Attribute{
Type: cty.String,
Optional: true,
Description: "File containing a web identity token from an OpenID Connect (OIDC) or OAuth provider.",
},
validateString{
Validators: []stringValidator{
validateStringLenBetween(4, 20000),
},
},
},
}
func (a singleNestedAttribute) Validator() validateSchema {
return a.validateObject
}
func deprecatedAttrDiag(attr, replacement cty.Path) tfdiags.Diagnostic {

@ -1869,14 +1869,28 @@ web_identity_token_file = no-such-file
config: map[string]any{
"assume_role_with_web_identity": map[string]any{},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Missing Required Value",
`Exactly one of web_identity_token, web_identity_token_file must be set.`,
cty.GetAttrPath("assume_role_with_web_identity"),
),
attributeErrDiag(
"Missing Required Value",
`The attribute "assume_role_with_web_identity.role_arn" is required by the backend.`+"\n\n"+
"Refer to the backend documentation for additional information which attributes are required.",
cty.GetAttrPath("assume_role_with_web_identity").GetAttr("role_arn"),
),
}),
},
"invalid no token": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
},
},
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Missing Required Value",
`Exactly one of web_identity_token, web_identity_token_file must be set.`,
@ -1885,17 +1899,19 @@ web_identity_token_file = no-such-file
}),
},
"invalid no token": {
"invalid token config conflict": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
"web_identity_token": servicemocks.MockWebIdentityToken,
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
SetConfig: true,
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Missing Required Value",
`Exactly one of web_identity_token, web_identity_token_file must be set.`,
"Invalid Attribute Combination",
`Only one of web_identity_token, web_identity_token_file can be set.`,
cty.GetAttrPath("assume_role_with_web_identity"),
),
}),

@ -2025,7 +2025,7 @@ func TestAssumeRole_PrepareConfigValidation(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
schema := assumeRoleFullSchema()
schema := assumeRoleSchema.Attributes
vals := make(map[string]cty.Value, len(schema))
for name, attrSchema := range schema {
if val, ok := tc.config[name]; ok {
@ -2036,7 +2036,8 @@ func TestAssumeRole_PrepareConfigValidation(t *testing.T) {
}
config := cty.ObjectVal(vals)
diags := prepareAssumeRoleConfig(config, path)
var diags tfdiags.Diagnostics
validateNestedAttribute(assumeRoleSchema, config, path, &diags)
if diff := cmp.Diff(diags, tc.expectedDiags, cmp.Comparer(diagnosticComparer)); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)

Loading…
Cancel
Save