Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 8 additions & 13 deletions src/cel-engine/src/rules/resources_extra.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,19 +312,14 @@ pub fn eval_extra_resources(ctx: &EvalContext) -> Vec<Diagnostic> {

if !ctx.cached_data.known_types.is_empty() {
for (name, res) in &m.resources {
if res.resource_type.is_empty() {
continue;
}
// Only custom resources (`Custom::` prefix) and modules (`::MODULE`
// suffix) are exempt from the known-type set; every other type —
// including all AWS-namespaced types such as `AWS::Serverless::*` and
// `AWS::CloudFormation::*` — must be a recognized type. Valid
// transform/service types already appear in `known_types`, so no
// namespace is exempted wholesale.
if !ctx.cached_data.known_types.contains(&res.resource_type)
&& !res.resource_type.starts_with("Custom::")
&& !res.resource_type.ends_with("::MODULE")
{
// An AWS-namespaced type absent from the compiled schema set is a
// typo or nonexistent type — CloudFormation owns the reserved
// `AWS::` namespace, so the embedded catalog is authoritative for
// it. Types in any other namespace (private registry types,
// `Custom::` resources, modules, hook-shaped names) may be
// registered per account/region, so they are skipped entirely
// rather than guessed at.
if res.resource_type.starts_with("AWS::") && !ctx.cached_data.known_types.contains(&res.resource_type) {
out.push(make_resource_diagnostic(
"F3006",
&format!("Unknown resource type '{}'", res.resource_type),
Expand Down
68 changes: 68 additions & 0 deletions src/cfn-validate/tests/github_issues.rs
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,74 @@ Resources:
assert_count(&diags, "W9009", 1);
}

/// Issue #185: a whole-number `Number` parameter default (30) reaching an
/// integer enum (LogGroup RetentionInDays) used to resolve as the float 30.0
/// and fail the enum comparison ("30.0 is not one of [1, 3, ..., 30, ...]").
/// Whole numbers now stay integers through the parameter path and numeric
/// scalars compare numerically, so W3030 must not fire on either the
/// parameter-sourced or the literal 30.0 form — cfn-lint is clean on both.
/// Handled in template-model, so both engines agree.
/// https://github.com/aws-cloudformation/cloudformation-validate/issues/185
#[test]
fn issue_185_no_w3030_on_number_parameter_default() {
let diags = validate_both("issue-185.yaml");
assert_absent(&diags, "W3030");
// The float-vs-integer confusion must not surface as a type finding either.
assert_absent(&diags, "F3012");
assert_absent(&diags, "W9003");
}

/// Issue #185 (positive boundary): a genuinely invalid retention value coming
/// through the same Number-parameter path must still fire W3030 — the numeric
/// equality fix must not silence real enum violations.
/// https://github.com/aws-cloudformation/cloudformation-validate/issues/185
#[test]
fn issue_185_w3030_still_fires_on_invalid_retention_via_parameter() {
const TEMPLATE: &[u8] = br#"
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
RetentionInDays:
Type: Number
Default: 31
Resources:
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: !Ref RetentionInDays
UpdateReplacePolicy: Retain
DeletionPolicy: Retain
"#;
let diags = validate_both_bytes(TEMPLATE);
assert_fires_with_severity(&diags, "W3030", Severity::Warn);
assert_count(&diags, "W3030", 1);
}

/// Issue #183: W3663 must not fire when a Lambda Permission's literal SourceArn
/// carries a proper 12-digit account id — that ARN pins the calling account by
/// itself. The issue's original ARN had a 10-digit account field, which is not
/// a valid account id: it cannot pin the account, so W3663 fires for that
/// resource only (and the ARN also fails the schema pattern, F3031) — matching
/// cfn-lint, whose extension schema uses the same ':\d{12}:' trigger.
/// https://github.com/aws-cloudformation/cloudformation-validate/issues/183
#[test]
fn issue_183_w3663_fires_only_for_source_arn_without_valid_account_id() {
let diags = validate_both("issue-183.yaml");
assert_count(&diags, "W3663", 1);
assert_fires_on_resource(&diags, "W3663", "PermissionInvalidAccountId");
assert_fires_with_severity(&diags, "W3663", Severity::Warn);
for (engine, d) in &diags {
let on_valid = d
.iter()
.filter(|x| x.rule_id == "W3663")
.any(|x| x.resource.as_ref().and_then(|r| r.id.as_deref()) == Some("PermissionWithAccountId"));
assert!(!on_valid, "[{engine}] W3663 must not fire on the permission whose ARN has a 12-digit account id");
}
// The invalid 10-digit account field also fails the ARN schema pattern,
// and only there — the valid ARN passes it.
assert_count(&diags, "F3031", 1);
assert_fires_on_resource(&diags, "F3031", "PermissionInvalidAccountId");
}

// ---------------------------------------------------------------------------
// Issue #36 — the IAM-role-ARN checks use a future-proof `arn:aws[a-zA-Z-]*`
// partition prefix, so ADC-partition ARNs no longer false-positive and the two
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,17 @@ package resources

import rego.v1

# F3006: Resource Type must be known. Only custom resources (`Custom::` prefix)
# and modules (`::MODULE` suffix) are exempt from the known-type set; every other
# type — including all AWS-namespaced types such as `AWS::Serverless::*` and
# `AWS::CloudFormation::*` — must be a recognized type. Valid transform/service
# types already appear in `known_resource_types`, so no namespace is exempted
# wholesale.
# F3006: An AWS-namespaced resource type that is not in the compiled schema
# set is a typo or nonexistent type — CloudFormation owns the reserved `AWS::`
# namespace, so the embedded schema catalog is authoritative for it. Types in
# any other namespace (private registry types, `Custom::` resources, modules,
# hook-shaped names) may be registered per account/region, so they are skipped
# entirely rather than guessed at.
violation contains make_diag("F3006", "FATAL", name,
sprintf("Unknown resource type '%s'", [rtype])) if {
some name, res in input.resources
rtype := res.resourceType
is_string(rtype)
rtype != ""
startswith(rtype, "AWS::")
not rtype in data.known_resource_types
not startswith(rtype, "Custom::")
not endswith(rtype, "::MODULE")
}
2 changes: 1 addition & 1 deletion src/rego-engine/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ fn e2e_bad_security_issues() {
#[test]
fn e2e_bad_unknown_properties() {
let report = validate_fixture("bad/unknown_properties.yaml");
assert!(has_rule(&report, "F3006"), "Expected F3006 for unknown type, got: {:?}", report.diagnostics);
assert!(has_rule(&report, "F3006"), "Expected F3006 for unknown AWS type, got: {:?}", report.diagnostics);
assert!(has_rule(&report, "F3002"), "Expected F3002 for unknown property, got: {:?}", report.diagnostics);
}

Expand Down
Loading
Loading