Skip to content

fix(cloudformation): resolve dynamic references for all template values - #3301

Open
omatheusmesmo wants to merge 1 commit into
floci-io:mainfrom
omatheusmesmo:fix/2213-cfn-dynamic-references
Open

fix(cloudformation): resolve dynamic references for all template values#3301
omatheusmesmo wants to merge 1 commit into
floci-io:mainfrom
omatheusmesmo:fix/2213-cfn-dynamic-references

Conversation

@omatheusmesmo

@omatheusmesmo omatheusmesmo commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Summary

CloudFormation dynamic references ({{resolve:ssm:...}}, {{resolve:secretsmanager:...}}) were only resolved for the RDS MasterUsername/MasterUserPassword properties, which call CfnDynamicReferences directly. Every other property value goes through CloudFormationTemplateEngine#resolveNode, which had no dynamic-reference stage at all, so a value such as a Lambda Environment.Variables entry reached the deployed resource as the literal {{resolve:ssm:...}} text instead of the resolved value.

Closes #2213

Type of change

  • Bug fix (fix:)
  • New feature (feat:)
  • Breaking change (feat!: or fix!:)
  • Docs / chore

AWS Compatibility

resolveNode now runs the same CfnDynamicReferences resolution over every textual result it produces: a plain string literal carrying the syntax outright, and the text an intrinsic function (e.g. Fn::Sub) produces. ssm-secure stays restricted to the RDS master-credential path, matching the existing behavior there. The RDS code path is unchanged, since it resolves MasterUsername/MasterUserPassword directly rather than through resolveNode.

  • CloudFormationTemplateEngine: added an optional dynamicReferenceResolver collaborator (a UnaryOperator, following the same nullable-functional-collaborator pattern already used for importValueResolver) and applied it in resolveNode for both plain string values and the output of resolved intrinsics.
  • CloudFormationService / CloudFormationResourceProvisioner: wired the new resolver into every place a CloudFormationTemplateEngine is constructed, backed by the existing CfnDynamicReferences bean.
  • Tests: unit tests in CloudFormationTemplateEngineTest covering the resolver wiring (dynamic reference in a plain string, in an Fn::Sub result, a plain literal left untouched, and no-resolver backward compatibility), plus a new CloudFormationDynamicReferenceIntegrationTest that deploys a real stack with a Lambda Environment.Variables entry using {{resolve:ssm:...}} and {{resolve:secretsmanager:...}} and asserts the deployed function has the resolved value, alongside a regression case for plain literal strings.

Checklist

  • ./mvnw test passes locally
  • New or updated integration test added
  • Commit messages follow Conventional Commits

@greptile-apps

greptile-apps Bot commented Sep 9, 2026

Copy link
Copy Markdown

Greptile Summary

This PR extends CloudFormation dynamic-reference resolution to scalar values, recursively resolved template nodes, stack outputs, and standalone Cloud Control provisioning.

  • Adds a general dynamic-reference resolver backed by the existing SSM and Secrets Manager implementation.
  • Routes both resolve and resolveNode textual results through that resolver.
  • Preserves ssm-secure references for the later RDS master-credential resolver.
  • Adds unit and integration coverage for scalar, nested, SSM, Secrets Manager, and literal values.

Confidence Score: 4/5

The PR is not yet safe to merge because unsupported ssm-secure references can silently reach non-RDS resources as literal values, and the integration test still violates the repository's SDK-validation requirement.

The earlier scalar-resolution finding is fully fixed by routing resolve through the dynamic-reference stage, and the RDS regression is fixed by preserving secure references until the dedicated RDS resolver runs. However, that preservation now applies to every property, allowing unsupported non-RDS ssm-secure references to bypass AWS-compatible validation. The previous SDK-validation finding also remains outstanding because the integration test continues to validate management APIs exclusively through handcrafted HTTP requests.

Files Needing Attention: src/main/java/io/github/hectorvent/floci/services/cloudformation/provisioners/CfnDynamicReferences.java; src/test/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationDynamicReferenceIntegrationTest.java

Important Files Changed

Filename Overview
src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationTemplateEngine.java Routes scalar and recursively resolved textual values through the optional dynamic-reference resolver, fixing the prior scalar-resolution gap.
src/main/java/io/github/hectorvent/floci/services/cloudformation/provisioners/CfnDynamicReferences.java Adds general dynamic-reference handling but incorrectly preserves unsupported ssm-secure references outside RDS master credentials.
src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationService.java Wires dynamic-reference resolution into resource and output template engines.
src/main/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationResourceProvisioner.java Wires standalone provisioning while retaining the separate RDS secure-reference resolution stage.
src/test/java/io/github/hectorvent/floci/services/cloudformation/CloudFormationDynamicReferenceIntegrationTest.java Adds end-to-end dynamic-reference coverage, but the previous repository-rule finding about raw HTTP management-plane validation remains outstanding.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[CloudFormation template value] --> B[Template engine resolve or resolveNode]
    B --> C{Contains dynamic reference?}
    C -->|No| D[Return original value]
    C -->|SSM or Secrets Manager| E[General dynamic-reference resolver]
    E --> F[Provision resolved property]
    C -->|ssm-secure| G[Preserve reference verbatim]
    G --> H{RDS master credential?}
    H -->|Yes| I[RDS resolver with allowSsmSecure]
    I --> F
    H -->|No| J[Literal placeholder reaches property]
Loading

Reviews (5): Last reviewed commit: "fix(cloudformation): resolve dynamic ref..." | Re-trigger Greptile

Comment on lines +145 to +156
if (node.isTextual()) {
return resolveDynamicReferences(node.textValue());
}
if (node.isNumber() || node.isBoolean()) {
return node;
}
if (node.isObject()) {
if (node.has("Ref") || node.has("Fn::Sub") || node.has("Fn::Join") ||
node.has("Fn::Select") || node.has("Fn::If") || node.has("Fn::Base64") ||
node.has("Fn::GetAtt") || node.has("Fn::ImportValue") || node.has("Fn::Split") ||
node.has("Fn::GetAZs") || node.has("Fn::Cidr") || node.has("Fn::FindInMap")) {
return TextNode.valueOf(resolve(node));
return resolveDynamicReferences(resolve(node));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Scalar properties bypass resolution

Dynamic references are resolved only through resolveNode, but many ordinary scalar properties still use resolve, which returns strings and intrinsic results without invoking the new resolver. For example, ProvisionContext.resolveOptional sends properties such as AWS::SNS::Topic.TopicName through engine.resolve, so {{resolve:ssm:...}} reaches the resource as literal text. This leaves the advertised all-property fix incomplete and violates the repository directive to preserve AWS-compatible CloudFormation behavior. Route scalar resolution through the same dynamic-reference stage and cover a directly resolved scalar property with a test.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@omatheusmesmo
omatheusmesmo force-pushed the fix/2213-cfn-dynamic-references branch from 2777a3a to fc39798 Compare September 9, 2026 15:33
@omatheusmesmo omatheusmesmo changed the title Resolve CloudFormation dynamic references for all template values fix(cloudformation): resolve dynamic references for all template values Sep 9, 2026
@omatheusmesmo
omatheusmesmo force-pushed the fix/2213-cfn-dynamic-references branch from fc39798 to f2c27de Compare September 9, 2026 17:29
Comment on lines +31 to +45
given()
.header("X-Amz-Target", "AmazonSSM.PutParameter")
.contentType(SSM_CONTENT_TYPE)
.body("""
{
"Name": "/cfn-dynref/url",
"Value": "https://real.example.com",
"Type": "String",
"Overwrite": true
}
""")
.when()
.post("/")
.then()
.statusCode(200);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 SDK Validation Is Missing

This integration test constructs raw SSM, Secrets Manager, CloudFormation, and Lambda HTTP requests. That violates the repository directive to prefer AWS SDK clients for management-plane validation, which ensures that tests exercise real client serialization and response handling. This repository requirement must be satisfied before merging. The same pattern also appears at lines 68–94, 99–160, and 164–216.

Context Used: AGENTS.md (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@omatheusmesmo
omatheusmesmo force-pushed the fix/2213-cfn-dynamic-references branch from f2c27de to 46bf29a Compare September 9, 2026 18:00
* substituted regardless of which of the two a caller uses.
*/
public String resolve(JsonNode node) {
return resolveDynamicReferences(resolveIntrinsic(node)).asText();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 RDS Secure References Fail

RDS ssm-secure credentials now fail before reaching the RDS-specific resolver. resolveOptional invokes engine.resolve, whose new general resolver disallows ssm-secure. The later call with allowSsmSecure=true is therefore never reached, so valid MasterUserPassword references for DB instances and clusters raise ValidationError.

@hectorvent hectorvent added bug Something isn't working cloudformation AWS CloudFormation labels Sep 9, 2026
@hectorvent hectorvent self-assigned this Sep 10, 2026
@hectorvent

Copy link
Copy Markdown
Collaborator

Thanks, this is a real gap and the fix is the right shape: dynamic references only worked where a provisioner called the resolver by hand, so {{resolve:ssm:...}} anywhere else reached the resource as literal text.

(blocking) The new general stage rejects the one case ssm-secure is legitimately allowed in. executeTemplate builds both engines with allowSsmSecure false, so every value passes that stage first, including an RDS MasterUserPassword, and CfnDynamicReferences throws ValidationError for ssm-secure whenever the flag is false. The reference is rejected one stage before the RDS path that would have resolved it, leaving the allowSsmSecure=true call site unreachable.

That is what CI shows: CloudFormationRdsDynamicReferenceTest.resolvesSecureSsmMasterPassword expects CREATE_COMPLETE and gets ROLLBACK_COMPLETE. It is the only failure; the other 1745 pass.

Your new javadoc is where the assumption shows: it says every other property reaches the resolver through the general path, but a master password is both. One direction if it helps: leave ssm-secure untouched in the general stage and let the property-specific path keep that call, since it alone knows the resource type and property.

@hectorvent hectorvent added the waiting-author Review posted; waiting on the PR author to respond label Sep 10, 2026
CloudFormationTemplateEngine#resolveNode now resolves {{resolve:ssm:...}}
and {{resolve:secretsmanager:...}} syntax in any textual result, not only
the RDS master-credential properties CfnDynamicReferences was previously
wired into. The dynamic-reference stage now lives inside resolve() itself,
so every scalar property resolution goes through it, including plain
property lookups such as ProvisionContext#resolveOptional that call
resolve() directly rather than through resolveNode. ssm-secure is left
verbatim at this general stage instead of rejected, since it is valid only
for RDS MasterUsername/MasterUserPassword, which resolve it separately
with the required permission.

* Fix floci-io#2213

Signed-off-by: Matheus Oliveira <matheus.6148@gmail.com>
@omatheusmesmo
omatheusmesmo force-pushed the fix/2213-cfn-dynamic-references branch from 46bf29a to 6bc36ac Compare September 10, 2026 11:50
Comment on lines +87 to +89
String replacement = "ssm-secure".equals(m.group(1))
? m.group(0)
: resolveDynamicRef(m.group(1), m.group(2), region, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Secure References Bypass Validation

The general resolver leaves every ssm-secure reference unchanged so the RDS credential path can resolve it later. However, non-RDS properties have no later validation stage, so an unsupported {{resolve:ssm-secure:...}} value is provisioned as a literal placeholder instead of producing the AWS-compatible validation error required by the repository's AWS compatibility directive. Restrict this pass-through to RDS master credentials or reject unresolved secure references before provisioning other properties.

Context Used: AGENTS.md (source)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working cloudformation AWS CloudFormation waiting-author Review posted; waiting on the PR author to respond

Projects

None yet

2 participants