Skip to content

Commit cd4ee03

Browse files
authored
sec(iac/aws): reject the all-zeros OIDC thumbprint in the aws-target bundles (#1678)
* sec(iac/aws): reject the all-zeros OIDC thumbprint in aws-target CloudFormation `OIDCThumbprint` defaulted to the all-zeros placeholder and was validated only as 40 hexadecimal characters, which that placeholder satisfies. Current AWS behaviour makes that value wrong in both directions. AWS verifies the JWKS endpoint's TLS certificate against its own library of trusted root CAs and consults the configured thumbprint only when that certificate does not chain to one of them, when AWS cannot retrieve it, or when the endpoint requires TLS 1.3. For both issuers this template documents (login.microsoftonline.com and accounts.google.com) the thumbprint is therefore never read at all. On the fallback path the all-zeros string is not the SHA-1 of any certificate, so it matches nothing and every AssumeRoleWithWebIdentity call fails. It does not bypass chain verification -- it nulls out the only verification that path has, while reading like a deliberate configuration. `ThumbprintList` is optional on AWS::IAM::OIDCProvider: when omitted, IAM retrieves and uses the issuer's real top intermediate CA thumbprint, which is what the IAM console does by default. That is now the template's default. - Default `OIDCThumbprint` to empty and omit `ThumbprintList` via `AWS::NoValue` when it is empty, so IAM retrieves the real thumbprint. - Reject the placeholder in two independent layers: an `AllowedPattern` requiring at least one non-zero hex digit, and an unconditional `Rules` assertion naming the literal. Neither a regex simplification nor a submission path that skips one layer can readmit it alone. - Replace the parameter documentation, which described the placeholder as safe for providers "where AWS auto-validates the chain". - Add regression coverage that reads the `AllowedPattern`, `MaxLength` and `Default` back out of the committed template and evaluates them the way CloudFormation does. Both tests fail against the pre-fix template. `ThumbprintList` updates with no interruption, so existing stacks switch between the two forms without replacing the OIDC provider or changing its ARN. Stacks currently holding the placeholder will fail their next update until the parameter is cleared or set to a real thumbprint; that is deliberate, and such a stack cannot be authenticating on the fallback path today in any case. Closes #1615 * sec(iac/aws): align aws-target Terraform thumbprint guard with AWS behaviour The `thumbprint_list` guard rejected the all-zeros placeholder for issuers other than `login.microsoftonline.com` and `accounts.google.com`, justified in the code as "the all-zeros value bypasses the CA-chain check entirely". That justification does not match how AWS handles thumbprints, and the CloudFormation sibling now encodes the opposite rule, so the two would otherwise contradict each other on the same defect. AWS verifies the JWKS endpoint's TLS certificate against its own library of trusted root CAs and falls back to the configured thumbprints only when that certificate does not chain to one of them, when AWS cannot retrieve it, or when the endpoint requires TLS 1.3. So all-zeros bypasses nothing: on the primary path AWS never reads it, and on the fallback path it is not the SHA-1 of any certificate and matches nothing, failing role assumption outright. The issuer allowlist was beside the point in both directions -- the thumbprint is unread for every publicly-trusted issuer, not only those two, and the placeholder breaks any private-CA issuer regardless of which one it is. - Default `thumbprint_list` to `[]` and pass `null` when empty, so IAM retrieves and uses the issuer's real top intermediate CA thumbprint. - Reject the all-zeros entry unconditionally instead of per-issuer, and drop the non-empty-list validation, since empty is now the correct default. - Record the finding, the corrected reasoning and the upgrade path in `known_issues/13_iac_aws_target.md`, and mark the superseded entry. Verified with `terraform validate` plus `terraform plan` over four inputs: the all-zeros list is rejected even for `accounts.google.com` (previously allowed), a short value is rejected, and both an empty list and a real thumbprint are accepted. Refs #1615 * docs(iac/aws): qualify the thumbprint fallback claim and tighten the guard test Review follow-up to the two commits above, no behaviour change. The parameter and variable documentation said the thumbprint is "never consulted" for the two documented issuers. AWS also falls back to thumbprint verification when it cannot retrieve the TLS certificate or when the endpoint requires TLS 1.3, and neither of those is issuer-specific, so the value can be read even for a publicly trusted issuer. Reworded to say it is read only when one of the listed fallback conditions applies. Also assert that the all-zeros placeholder appears exactly once in the template, so it can live in the Rules assertion that rejects it and nowhere else; a `Contains` check would have stayed green if it were reintroduced as a default alongside the assertion. Refs #1615
1 parent f6bb36a commit cd4ee03

5 files changed

Lines changed: 350 additions & 35 deletions

File tree

‎iac/federation/aws-target/cloudformation/template.yaml‎

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,46 @@ Parameters:
5353
OIDCThumbprint:
5454
Type: String
5555
Description: >
56-
SHA-1 thumbprint of the OIDC provider root CA certificate.
57-
For GCP (accounts.google.com) use: 08745487e891c19e3078c1f2a07e452950ef36f6
58-
For Azure AD use the thumbprint of the DigiCert Global Root CA.
59-
Leave as zeros only for well-known providers where AWS auto-validates the chain.
60-
Default: "0000000000000000000000000000000000000000"
61-
AllowedPattern: "^[0-9a-fA-F]{40}$"
56+
OPTIONAL. SHA-1 thumbprint (40 hex characters) of the top intermediate CA
57+
that signed the TLS certificate of the issuer's JWKS endpoint.
58+
LEAVE THIS EMPTY unless that certificate does not chain to a publicly
59+
trusted CA. Empty omits the ThumbprintList property entirely, and IAM then
60+
retrieves the correct thumbprint itself, which is what the IAM console
61+
does by default.
62+
AWS verifies the JWKS endpoint's TLS certificate against its own library of
63+
trusted root CAs, and falls back to this thumbprint only when that
64+
certificate does not chain to one of them, when AWS cannot retrieve the
65+
certificate, or when the endpoint requires TLS 1.3. Both documented
66+
issuers (login.microsoftonline.com and accounts.google.com) present
67+
publicly trusted certificates, so for them the value is read only if one
68+
of those fallback conditions applies, whatever it is set to.
69+
The all-zeros placeholder that used to be the default is now rejected. It
70+
does not disable chain verification. It is simply not the SHA-1 of any
71+
certificate, so on the fallback path it matches nothing and every
72+
AssumeRoleWithWebIdentity call fails. It nulls out the only check that
73+
path has while reading like a deliberate configuration.
74+
Note: when an issuer's discovery endpoint and jwks_uri are on different
75+
hosts, AWS requires the thumbprints of BOTH. This parameter carries a
76+
single value, so such an issuer must be onboarded with the AWS CLI or the
77+
IAM console instead of this template.
78+
Default: ""
79+
MaxLength: 40
80+
# Empty, or up to 40 hex characters of which at least one is non-zero.
81+
# The middle character class is what rejects the all-zeros placeholder: do
82+
# not "simplify" this back to ^$|^[0-9a-fA-F]{40}$, which readmits it.
83+
# MaxLength caps the upper end (the pattern alone would admit up to 79
84+
# characters). A well-formed-but-short value (1-39 hex) passes here and is
85+
# rejected by the IAM API, which fixes the length at 40: fail-closed, with
86+
# the stack rolling back rather than deploying a broken provider.
87+
AllowedPattern: "^$|^[0-9a-fA-F]{0,39}[1-9a-fA-F][0-9a-fA-F]{0,39}$"
88+
ConstraintDescription: >
89+
OIDCThumbprint must be empty (recommended, since IAM then retrieves the
90+
thumbprint itself), or a 40-character hex SHA-1 thumbprint containing at
91+
least one non-zero digit.
92+
The all-zeros placeholder is rejected because it is not the fingerprint of
93+
any certificate: whenever AWS actually falls back to thumbprint
94+
verification it matches nothing, so role assumption fails outright. Supply
95+
the issuer's real thumbprint, or leave this empty.
6296
6397
OIDCSubjectClaim:
6498
Type: String
@@ -99,6 +133,26 @@ Parameters:
99133

100134
Conditions:
101135
HasAudience: !Not [!Equals [!Ref OIDCAudience, ""]]
136+
HasThumbprint: !Not [!Equals [!Ref OIDCThumbprint, ""]]
137+
138+
# Rules are evaluated before CloudFormation creates or updates any resource; a
139+
# failed assertion aborts the stack operation. This duplicates the all-zeros
140+
# rejection already encoded in the OIDCThumbprint AllowedPattern on purpose:
141+
# the two are independent, so neither a regex "simplification" nor a submission
142+
# path that skips one of the layers can readmit the placeholder on its own.
143+
Rules:
144+
RejectPlaceholderThumbprint:
145+
Assertions:
146+
- Assert: !Not
147+
- !Equals
148+
- !Ref OIDCThumbprint
149+
- "0000000000000000000000000000000000000000"
150+
AssertDescription: >
151+
OIDCThumbprint must not be the all-zeros placeholder. It is not the
152+
SHA-1 fingerprint of any certificate, so on the one path where AWS
153+
still consults a thumbprint it matches nothing and every
154+
AssumeRoleWithWebIdentity call fails. Leave OIDCThumbprint empty to
155+
have IAM retrieve the real thumbprint, or supply the issuer's own.
102156
103157
Resources:
104158
OIDCProvider:
@@ -107,8 +161,15 @@ Resources:
107161
Url: !Ref OIDCIssuerURL
108162
ClientIdList:
109163
- !If [HasAudience, !Ref OIDCAudience, sts.amazonaws.com]
110-
ThumbprintList:
111-
- !Ref OIDCThumbprint
164+
# Omitted entirely when OIDCThumbprint is empty, so IAM retrieves and uses
165+
# the issuer's real top intermediate CA thumbprint instead of whatever
166+
# placeholder the operator was handed. ThumbprintList is optional on
167+
# AWS::IAM::OIDCProvider and updates with no interruption, so switching
168+
# between the two forms never replaces the provider or changes its ARN.
169+
ThumbprintList: !If
170+
- HasThumbprint
171+
- [!Ref OIDCThumbprint]
172+
- !Ref "AWS::NoValue"
112173

113174
CUDlyPolicy:
114175
Type: AWS::IAM::ManagedPolicy

‎iac/federation/aws-target/terraform/main.tf‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,20 @@ locals {
2323
}
2424

2525
resource "aws_iam_openid_connect_provider" "cudly" {
26-
url = local.oidc_issuer_url_normalized
27-
client_id_list = [local.audience]
28-
thumbprint_list = var.thumbprint_list
26+
url = local.oidc_issuer_url_normalized
27+
client_id_list = [local.audience]
28+
29+
# null, not [], when no thumbprint is configured: thumbprint_list is optional
30+
# on this resource, and leaving it unset is what makes IAM retrieve and use
31+
# the issuer's real top intermediate CA thumbprint. An empty list would
32+
# instead send a provider that has no thumbprints at all.
33+
#
34+
# Note for existing deployments: the argument is Optional+Computed, so
35+
# clearing it does not clear the value already stored on an existing
36+
# provider -- Terraform keeps reading back what is there. A provider created
37+
# with the old all-zeros default must be corrected out of band with
38+
# `aws iam update-open-id-connect-provider-thumbprint`, or replaced.
39+
thumbprint_list = length(var.thumbprint_list) > 0 ? var.thumbprint_list : null
2940
}
3041

3142
resource "aws_iam_policy" "cudly" {

‎iac/federation/aws-target/terraform/variables.tf‎

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,26 @@ variable "role_name" {
8686

8787
variable "thumbprint_list" {
8888
description = <<-EOT
89-
TLS root CA thumbprints for the OIDC provider (40-character hex SHA-1).
90-
AWS auto-validates well-known providers (Azure AD, Google); for those the
91-
all-zeros placeholder is intentional and accepted.
92-
For any other issuer you MUST supply the real root CA SHA-1 thumbprint.
93-
Supplying the all-zeros placeholder for a custom issuer is rejected by this
94-
module to prevent operators from silently bypassing CA-chain validation.
89+
OPTIONAL. TLS thumbprints (40-character hex SHA-1) of the top intermediate
90+
CA that signed the certificate of the issuer's JWKS endpoint.
91+
92+
LEAVE THIS EMPTY unless that certificate does not chain to a publicly
93+
trusted CA. An empty list omits the argument entirely, and IAM then
94+
retrieves the correct thumbprint itself.
95+
96+
AWS verifies the JWKS endpoint's TLS certificate against its own library of
97+
trusted root CAs, and falls back to these thumbprints only when that
98+
certificate does not chain to one of them, when AWS cannot retrieve the
99+
certificate, or when the endpoint requires TLS 1.3. Both documented
100+
issuers (login.microsoftonline.com and accounts.google.com) present
101+
publicly trusted certificates, so for them the value is read only if one of
102+
those fallback conditions applies, whatever it is set to.
103+
104+
When the issuer's discovery endpoint and jwks_uri are on different hosts,
105+
AWS requires the thumbprints of BOTH; supply both entries in that case.
95106
EOT
96107
type = list(string)
97-
default = ["0000000000000000000000000000000000000000"]
98-
99-
validation {
100-
condition = length(var.thumbprint_list) > 0
101-
error_message = "thumbprint_list must contain at least one thumbprint."
102-
}
108+
default = []
103109

104110
validation {
105111
condition = alltrue([
@@ -108,18 +114,22 @@ variable "thumbprint_list" {
108114
error_message = "Each thumbprint in thumbprint_list must be a 40-character SHA-1 hex string."
109115
}
110116

111-
# Guard against copy-paste of the all-zeros placeholder for custom OIDC
112-
# issuers. AWS natively validates Azure AD and Google endpoints, so
113-
# all-zeros is safe for those. Any other issuer URL requires a real CA
114-
# thumbprint; the all-zeros value bypasses the CA-chain check entirely.
117+
# The all-zeros placeholder used to be this variable's default, guarded by an
118+
# issuer allowlist justified as "the all-zeros value bypasses the CA-chain
119+
# check entirely". That justification was wrong in both directions, so the
120+
# guard is now unconditional and the default is empty.
121+
#
122+
# All-zeros bypasses nothing: it is simply not the SHA-1 of any certificate.
123+
# On the primary path AWS does not read it at all, and on the fallback path it
124+
# matches nothing, so role assumption fails outright. Restricting it to Azure AD and
125+
# Google was equally beside the point -- the thumbprint is unread for every
126+
# publicly-trusted issuer, not only those two, and setting it for a
127+
# private-CA issuer breaks that issuer no matter which one it is.
115128
validation {
116-
condition = !(
117-
length(var.thumbprint_list) == 1 &&
118-
var.thumbprint_list[0] == "0000000000000000000000000000000000000000" &&
119-
!startswith(var.oidc_issuer_url, "https://login.microsoftonline.com/") &&
120-
!startswith(var.oidc_issuer_url, "https://accounts.google.com")
121-
)
122-
error_message = "thumbprint_list is the all-zeros placeholder, which is only safe for Azure AD (login.microsoftonline.com) and Google (accounts.google.com) issuers that AWS validates natively. For any other OIDC issuer you must supply the real root CA SHA-1 thumbprint."
129+
condition = alltrue([
130+
for t in var.thumbprint_list : t != "0000000000000000000000000000000000000000"
131+
])
132+
error_message = "thumbprint_list must not contain the all-zeros placeholder. It is not the fingerprint of any certificate, so whenever AWS actually falls back to thumbprint verification it matches nothing and role assumption fails. Leave thumbprint_list empty to have IAM retrieve the issuer's real thumbprint, or supply that thumbprint."
123133
}
124134
}
125135

‎internal/api/handler_federation_test.go‎

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import (
88
"encoding/json"
99
"io/fs"
1010
"os"
11+
"regexp"
12+
"strconv"
1113
"strings"
1214
"testing"
1315

1416
"github.com/aws/aws-lambda-go/events"
1517
"github.com/stretchr/testify/assert"
1618
"github.com/stretchr/testify/mock"
1719
"github.com/stretchr/testify/require"
20+
"gopkg.in/yaml.v3"
1821
)
1922

2023
// federationHandler returns a Handler wired for federation IaC tests.
@@ -1499,3 +1502,154 @@ func TestValidateFederationTargetSource(t *testing.T) {
14991502
})
15001503
}
15011504
}
1505+
1506+
// ---------------------------------------------------------------------------
1507+
// aws-target OIDC provider thumbprint guard (issue #1615)
1508+
// ---------------------------------------------------------------------------
1509+
1510+
const awsTargetCFNTemplate = "../../iac/federation/aws-target/cloudformation/template.yaml"
1511+
1512+
// allZeroThumbprint is the placeholder that used to be the OIDCThumbprint
1513+
// default. It is not the SHA-1 fingerprint of any certificate, so on the one
1514+
// path where AWS still consults a thumbprint (the JWKS endpoint's TLS
1515+
// certificate does not chain to a CA in AWS's trusted-root library, AWS cannot
1516+
// retrieve that certificate, or the endpoint requires TLS 1.3) it matches
1517+
// nothing and every AssumeRoleWithWebIdentity call fails.
1518+
const allZeroThumbprint = "0000000000000000000000000000000000000000"
1519+
1520+
// cfnParamBlock returns the lines of a top-level Parameters entry, from
1521+
// " <name>:" up to the next sibling key at the same indent.
1522+
func cfnParamBlock(t *testing.T, src, name string) string {
1523+
t.Helper()
1524+
start := strings.Index(src, "\n "+name+":\n")
1525+
require.GreaterOrEqual(t, start, 0, "parameter %s not found in %s", name, awsTargetCFNTemplate)
1526+
rest := src[start+1:]
1527+
lines := strings.Split(rest, "\n")
1528+
var out []string
1529+
for i, line := range lines {
1530+
if i > 0 && len(line) > 2 && line[0] == ' ' && line[1] == ' ' && line[2] != ' ' {
1531+
break
1532+
}
1533+
out = append(out, line)
1534+
}
1535+
return strings.Join(out, "\n")
1536+
}
1537+
1538+
// cfnScalar pulls a scalar property out of a parameter block and decodes it
1539+
// with the YAML decoder, so the value under test is exactly what CloudFormation
1540+
// receives rather than a copy retyped into this test.
1541+
func cfnScalar(t *testing.T, block, key string) string {
1542+
t.Helper()
1543+
for _, line := range strings.Split(block, "\n") {
1544+
trimmed := strings.TrimSpace(line)
1545+
if !strings.HasPrefix(trimmed, key+":") {
1546+
continue
1547+
}
1548+
var holder struct {
1549+
V string `yaml:"v"`
1550+
}
1551+
require.NoError(t, yaml.Unmarshal([]byte("v:"+strings.TrimPrefix(trimmed, key+":")), &holder),
1552+
"could not YAML-decode %s from %q", key, trimmed)
1553+
return holder.V
1554+
}
1555+
t.Fatalf("key %s not found in parameter block:\n%s", key, block)
1556+
return ""
1557+
}
1558+
1559+
// TestAWSTargetTemplate_RejectsPlaceholderThumbprint is the regression test for
1560+
// issue #1615. The template used to default OIDCThumbprint to the all-zeros
1561+
// placeholder and validate it only as 40 hex characters, which that placeholder
1562+
// satisfies.
1563+
//
1564+
// The AllowedPattern and MaxLength are read back out of the committed template
1565+
// and evaluated the way CloudFormation evaluates them (anchored full match plus
1566+
// the length cap), so a future edit that relaxes either one fails here.
1567+
func TestAWSTargetTemplate_RejectsPlaceholderThumbprint(t *testing.T) {
1568+
raw, err := os.ReadFile(awsTargetCFNTemplate)
1569+
require.NoError(t, err)
1570+
src := string(raw)
1571+
1572+
block := cfnParamBlock(t, src, "OIDCThumbprint")
1573+
1574+
// The placeholder must not be reachable by leaving the parameter alone.
1575+
require.Equal(t, "", cfnScalar(t, block, "Default"),
1576+
"OIDCThumbprint must default to empty so ThumbprintList is omitted and IAM "+
1577+
"retrieves the issuer's real thumbprint")
1578+
1579+
pattern := cfnScalar(t, block, "AllowedPattern")
1580+
maxLen, err := strconv.Atoi(cfnScalar(t, block, "MaxLength"))
1581+
require.NoError(t, err, "MaxLength must be an integer")
1582+
1583+
re, err := regexp.Compile(pattern)
1584+
require.NoError(t, err, "AllowedPattern must be a valid regular expression")
1585+
1586+
// CloudFormation applies AllowedPattern as a full match and MaxLength
1587+
// independently; a value is accepted only when it clears both.
1588+
accepted := func(v string) bool {
1589+
return len(v) <= maxLen && re.FindString(v) == v && re.MatchString(v)
1590+
}
1591+
1592+
mustAccept := []struct{ value, why string }{
1593+
{"", "empty: ThumbprintList is omitted and IAM retrieves the real thumbprint"},
1594+
{"08745487e891c19e3078c1f2a07e452950ef36f6", "a real root CA thumbprint"},
1595+
{"990F4193972F2BECF12DDEDA5237F9C952F20D9E", "uppercase hex"},
1596+
{"1" + strings.Repeat("0", 39), "non-zero digit in the first position only"},
1597+
{strings.Repeat("0", 39) + "1", "non-zero digit in the last position only"},
1598+
{strings.Repeat("0", 20) + "9" + strings.Repeat("0", 19), "non-zero digit in the middle only"},
1599+
{strings.Repeat("0", 39) + "F", "non-zero position holding an uppercase hex letter"},
1600+
{strings.Repeat("0", 39) + "a", "non-zero position holding a lowercase hex letter"},
1601+
}
1602+
for _, tc := range mustAccept {
1603+
assert.True(t, accepted(tc.value), "must accept %q (%s)", tc.value, tc.why)
1604+
}
1605+
1606+
mustReject := []struct{ value, why string }{
1607+
{allZeroThumbprint, "the #1615 placeholder itself"},
1608+
{strings.Repeat("0", 39), "all zeros, one character short"},
1609+
{strings.Repeat("0", 41), "all zeros, one character long"},
1610+
{"0", "a single zero"},
1611+
{strings.Repeat("a", 41), "41 hex characters: caught by MaxLength, not the pattern"},
1612+
{strings.Repeat("a", 40) + "\n", "trailing newline: MaxLength rejects it even if $ matched before it"},
1613+
{strings.Repeat("g", 40), "not hexadecimal"},
1614+
{"0x" + strings.Repeat("0", 38), "hex-literal prefix"},
1615+
{" " + strings.Repeat("a", 39), "leading whitespace"},
1616+
{strings.Repeat("a", 39) + " ", "trailing whitespace"},
1617+
{"*", "wildcard"},
1618+
{"${accounts.google.com:sub}", "an IAM policy variable"},
1619+
}
1620+
for _, tc := range mustReject {
1621+
assert.False(t, accepted(tc.value), "must reject %q (%s)", tc.value, tc.why)
1622+
}
1623+
}
1624+
1625+
// TestAWSTargetTemplate_ThumbprintWiring asserts the second, independent layer
1626+
// of the #1615 guard and the resource wiring it protects. The Rules assertion
1627+
// duplicates the AllowedPattern's rejection of the placeholder on purpose, so
1628+
// neither a regex "simplification" nor a submission path that skips one layer
1629+
// can readmit it alone.
1630+
func TestAWSTargetTemplate_ThumbprintWiring(t *testing.T) {
1631+
raw, err := os.ReadFile(awsTargetCFNTemplate)
1632+
require.NoError(t, err)
1633+
src := string(raw)
1634+
1635+
assert.Contains(t, src, "Rules:",
1636+
"template must declare a Rules section; rules are evaluated before any resource is created")
1637+
assert.Equal(t, 1, strings.Count(src, allZeroThumbprint),
1638+
"the all-zeros placeholder must appear exactly once in the template: in the "+
1639+
"Rules assertion that rejects it, and nowhere else")
1640+
assert.Contains(t, src, `HasThumbprint: !Not [!Equals [!Ref OIDCThumbprint, ""]]`,
1641+
"HasThumbprint must be defined as a non-empty OIDCThumbprint")
1642+
1643+
// The empty branch must omit ThumbprintList entirely rather than send an
1644+
// empty or placeholder list, which is what makes IAM retrieve the real
1645+
// thumbprint for itself.
1646+
assert.Contains(t, src, `ThumbprintList: !If`,
1647+
"ThumbprintList must be conditional on HasThumbprint")
1648+
assert.Contains(t, src, `- !Ref "AWS::NoValue"`,
1649+
"the empty branch must resolve to AWS::NoValue so the property is omitted")
1650+
1651+
// The placeholder must appear only inside the Rules assertion that rejects
1652+
// it, never as a parameter default or a resource value.
1653+
assert.NotContains(t, src, `Default: "`+allZeroThumbprint+`"`,
1654+
"the all-zeros placeholder must not be reintroduced as a default")
1655+
}

0 commit comments

Comments
 (0)