Skip to content

CFN detector parity audit

This page is the load-bearing matrix that lets us be precise about what "CloudFormation supported" means in Efterlev. Per the v0.1.95 graduation plan: each of the 60 KSI-mapped detectors is classified by whether it fires on CFN-translated bodies equivalently to Terraform.

The matrix is generated by scripts/build_cfn_parity_table.py, which inspects:

  1. The registered CFN→TF mappings in src/efterlev/cloudformation/property_mapping.py's _MAPPINGS dict.
  2. Each mapping function's emitted tf_type (handles 1→1+N synthesis for IAM/S3/Events).
  3. Each detector's r.type == filter (which TF resource types it reads).
  4. Manual-review tables for:
  5. TF type aliases (aws_albaws_lb, aws_alb_listeneraws_lb_listener).
  6. TF data sources with no CFN concept (aws_iam_policy_document).
  7. TF-only resources with no CFN counterpart in AWS at all (aws_iam_account_password_policy — account-level setting, deployable only via the IAM API).
  8. TF sub-resources CFN bundles inline (aws_s3_bucket_versioning etc., where CFN's AWS::S3::Bucket carries the same property data in a nested block).

Coverage summary at v0.1.96

Authoritative data: docs/cfn-detector-parity.csv.

Status Count Meaning
full 47 Every TF type the detector reads has a mapped CFN equivalent. Detector fires on CFN identically to TF.
partial-by-design 11 Some TF types are mapped; the unmapped ones are either CFN-bundled-into-parent (S3 sub-resources, etc.) or TF-only-by-design (data sources). No real coverage gap.
TF-only-by-design 2 Detector reads only TF-side concepts (data sources, account-level settings, IAM-API-only resources). No CFN equivalent exists.

Effective CFN coverage: 100% (full + partial-by-design + TF-only-by-design = 60 of 60). Zero real gaps.

v0.1.96 closed the v0.1.95 gaps

The v0.1.95 audit surfaced 5 detectors with real CFN coverage gaps (8 unmapped CFN types total). v0.1.96 (PR gamma.2 batch 9) closed all of them by mapping AWS::IAM::AccessKey, AWS::Logs::Destination, AWS::Logs::SubscriptionFilter, AWS::OpenSearchService::Domain, AWS::Elasticsearch::Domain, AWS::KinesisFirehose::DeliveryStream, AWS::SecurityHub::Hub, and AWS::SecurityHub::FindingAggregator.

TF-only by design (intentional, no fix planned)

Detector Reason
aws.iam_password_policy The IAM account password policy is an account-level setting in AWS, deployable only via the IAM API or AWS Console. CFN has no resource type for it. TF's aws_iam_account_password_policy abstracts the API call. CFN users hit this control via the IAM API directly — outside Efterlev's IaC scope.
aws.iam_managed_via_terraform Meta-detector: confirms the workspace declares any aws_iam_* resources at all. By definition Terraform-only — exists to call out boundary scope when a workspace has zero IAM in TF.
aws.iam_service_account_keys_age (partial-by-design) Reads aws_iam_access_key (now CFN-mapped at v0.1.96) AND aws_iam_user_login_profile (no CFN equivalent — AWS exposes login-profile creation only via the IAM API). The detector still fires on CFN-translated AccessKey; the user-login-profile read path is a TF-only data point.

Sub-resource bundling: how the CFN side handles TF separation-of-concerns

Terraform splits some AWS resources into multiple separate TF resource types where CloudFormation bundles them inline. For example, aws_s3_bucket + aws_s3_bucket_versioning + aws_s3_bucket_server_side_encryption_configuration + ... are all modeled as nested blocks under CFN's AWS::S3::Bucket.

The property mapping table handles this in two ways:

  • 1→1+N synthesis when the detector reads the sub-resource type directly. v0.1.73's _map_s3_bucket synthesizes aws_s3_bucket_public_access_block from PublicAccessBlockConfiguration; gamma.2 batch 2 (v0.1.75) does the same for aws_iam_role_policy_attachment per ManagedPolicyArns entry, etc.
  • Parent-body access when the detector reads the property from the parent type's body. aws.encryption_s3_at_rest reads aws_s3_bucket.server_side_encryption_configuration (or the separate aws_s3_bucket_server_side_encryption_configuration resource); the CFN side surfaces both via the parent bucket's body.

Sub-resources that v0.1.95 doesn't yet auto-synthesize but bundles into the parent CFN type:

TF sub-resource Parent CFN type Status
aws_s3_bucket_versioning AWS::S3::Bucket Bundled in parent body via VersioningConfiguration; not synthesized. Detectors that reach into the parent body see the data; standalone-resource lookups don't.
aws_s3_bucket_acl AWS::S3::Bucket Bundled via AccessControl; not synthesized.
aws_s3_bucket_server_side_encryption_configuration AWS::S3::Bucket Bundled via BucketEncryption; not synthesized.
aws_s3_bucket_lifecycle_configuration AWS::S3::Bucket Bundled via LifecycleConfiguration; not synthesized.
aws_s3_bucket_public_access_block AWS::S3::Bucket ALREADY SYNTHESIZED via 1→1+N from batch 1 (v0.1.74). Detectors that filter on this type fire on CFN.
aws_s3_bucket_replication_configuration AWS::S3::Bucket Bundled via ReplicationConfiguration; not synthesized.
aws_rds_cluster_instance AWS::RDS::DBCluster CFN clusters bundle member instances via separate AWS::RDS::DBInstance with DBClusterIdentifier; not auto-synthesized as aws_rds_cluster_instance.

Regenerating the matrix

uv run python scripts/build_cfn_parity_table.py

Outputs docs/cfn-detector-parity.csv (authoritative) and reports a status breakdown. Re-run whenever a new detector lands or the property mapping table changes; the matrix should stay in sync.