Skip to content

feat(formats): add AWS::IAM::Role.Name format keyword as E1165 - #4936

Merged
kddejong merged 1 commit into
aws-cloudformation:mainfrom
kddejong:feat/iam-role-name-format
Oct 27, 2026
Merged

kddejong merged 1 commit into
aws-cloudformation:mainfrom
kddejong:feat/iam-role-name-format

Conversation

@kddejong

Copy link
Copy Markdown
Contributor

Problem

Several properties accept an IAM role as either an ARN or a bare role name. The measured cases are AWS::Batch::ComputeEnvironment ServiceRole and ComputeResources/SpotIamFleetRole. A schema expresses that with both halves of an anyOf:

{"anyOf": [{"format": "AWS::IAM::Role.Arn"}, {"format": "AWS::IAM::Role.Name"}]}

The ARN half already exists as E1156. The name half did not, so that branch accepted any string and a schema change using it would have been silently unenforced.

This is step 1 of 2. A matching resource-provider-enhanced-schemas change depends on this landing first. An earlier attempt on the schema side inlined a raw pattern; a named format is preferable because that repo's output has consumers beyond cfn-lint, and a format carries the semantic concept where a regex is opaque.

The change

E1165 / AWS::IAM::Role.Name, mirroring IamRoleArn.py (E1156). This follows the three existing .Name formats — AWS::EC2::SecurityGroup.Name (E1153), AWS::Logs::LogGroup.Name (E1155), AWS::S3::Bucket.Name (E1161).

^(/[!-~]+/)?[a-zA-Z0-9_+=,.@-]{1,64}$

Both halves are IAM's own published specs:

  • the name part is IAM's RoleName, [\w+=,.@-]{1,64}
  • the optional leading group is IAM's Path, (\u002F)|(\u002F[\u0021-\u007E]+\u002F), because the accepting APIs take the path and the name concatenated. Printable ASCII includes /, so nested service-linked role paths match too.

\w is spelled out in ASCII deliberately: it is Unicode-aware in Python but ASCII-only in ECMA-262, and these patterns are consumed by both. Two tests pin the readable spelling to the spec form so it cannot drift — the path form is string-identical once the escapes resolve, and the name class has zero disagreements against [\w+=,.@-] read with ECMA-262 semantics across all 128 ASCII code points. A third test pins the consequence: Python's \w accepts é, 中 and ١, and IAM does not.

Rule id

E1165. E1163 is a retired id — it was LambdaFunctionName / AWS::Lambda::Function.Name, removed in #4529 — so it was left alone rather than recycled, since reusing it would collide with any existing user suppression. E1164 (KmsAliasName) is the current ceiling; E1165 has no occurrence anywhere in the tree or on main.

Evidence for the pattern

Value Expected Result
AWSBatchServiceRole pass pass
/foo/bar pass pass
/service-role/AWSBatchServiceRole pass pass
/aws-service-role/spotfleet.amazonaws.com/AWSServiceRoleForEC2SpotFleet pass pass
role-with-plus+equals=comma,dot.at@dash- pass pass
64 × a (boundary) pass pass
65 × a fail fail
`` (empty) fail fail
/ fail fail
/foo/ fail fail
arn:aws:iam::123456789012:role/MyRole fail fail
bad name with spaces fail fail

The ARN is rejected here on purpose: : is outside the name charset, and matching it is the other branch's job.

Verification

cfn-lint --update-specs first, then:

python -m pytest test/unit/rules/formats/test_iam_role_name.py   # 24 passed
python -m pytest test                                            # 3212 passed, 19 failed
pre-commit run --all-files                                       # all hooks pass, mypy included
cfn-lint -t "test/fixtures/templates/good/**/*.yaml"             # no new findings

The 19 suite failures are pre-existing and come from optional dependencies absent locally (pydot, pygraphviz, sarif_om). Confirmed rather than assumed: stashing only this change's paths and re-running produced a byte-identical failure set, with the pass count dropping by exactly the 24 new tests.

The good-fixture sweep was also diffed against the same tree with the rule file removed. The findings are byte-identical at 162 either way, so this introduces zero new findings.

Enforcement, end to end

Registration was not taken on faith. cfn-lint -l lists E1165, and a scratch registry schema (-s) carrying format: AWS::IAM::Role.Name was linted for real:

  • invalid values — spaces, 65 chars, an ARN in the name slot, /foo/ — each raise E1165 at the right line
  • every valid value from the table above is silent
  • unresolvable values are silent: !Ref, !GetAtt, !Sub, and !If, matching the existing format rules, since FormatKeyword.format returns True for anything that is not a string

The real anyOf shape was exercised too. A bare name, an ARN, the path form, !GetAtt Role.Arn and an unresolved !Ref are all accepted, and only a genuinely invalid value is rejected — by both branches, surfacing as E1156 + E1165 + E3017.

The counterfactual confirms the rule is load-bearing: the same anyOf with an unregistered format name on the second branch exits 0 on bad name with spaces, silently accepting it.

What is not proven here: no real resource schema ships format: AWS::IAM::Role.Name yet, so enforcement on AWS::Batch::ComputeEnvironment itself cannot be observed until the enhanced-schemas change lands. The scratch registry schema is a stand-in for it.

One thing worth flagging for the schema side: declaring a pure .Name format on a property makes E1040 compare GetAtt source formats against it, so !GetAtt Role.Arn into a name-only slot is flagged. Under the intended anyOf the ARN branch satisfies it and no E1040 appears, which is the behaviour observed above.

Several properties accept an IAM role as either an ARN or a bare role
name, for example AWS::Batch::ComputeEnvironment ServiceRole and
ComputeResources/SpotIamFleetRole. Expressing that in a schema needs both
halves of an anyOf:

    {"anyOf": [{"format": "AWS::IAM::Role.Arn"},
                {"format": "AWS::IAM::Role.Name"}]}

The ARN half already exists as E1156. The name half did not, so that
branch accepted any string and the schema change would have been
silently unenforced.

The pattern is ^(/[!-~]+/)?[a-zA-Z0-9_+=,.@-]{1,64}$. The name part is
IAM's RoleName spec [\w+=,.@-]{1,64} and the optional leading group is
IAM's Path spec (\u002F)|(\u002F[\u0021-\u007E]+\u002F), because the
accepting APIs take the path and the name concatenated. Printable ASCII
includes /, so nested service-linked role paths match too.

\w is spelled out in ASCII because it is Unicode-aware in Python but
ASCII-only in ECMA-262, and these patterns are consumed by both. Tests
assert the literal spelling agrees with the escaped spec form over every
ASCII code point so the readable form cannot drift.
@codecov

codecov Bot commented Oct 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.68%. Comparing base (0a02f1b) to head (120ef4a).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #4936   +/-   ##
=======================================
  Coverage   94.68%   94.68%           
=======================================
  Files         441      442    +1     
  Lines       16067    16077   +10     
  Branches     3116     3116           
=======================================
+ Hits        15213    15223   +10     
  Misses        470      470           
  Partials      384      384           
Flag Coverage Δ
unittests 94.68% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kddejong
kddejong marked this pull request as ready for review October 27, 2026 14:41
@kddejong
kddejong merged commit 9b0e661 into aws-cloudformation:main Oct 27, 2026
23 of 24 checks passed
@kddejong
kddejong deleted the feat/iam-role-name-format branch October 27, 2026 15:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant