Mapping AWS Foundational Security Best Practices into Pre-Deploy Checks
Turn AWS Security Hub FSBP into CI/CD gates with policy-as-code for CloudFormation and Terraform.
Security Hub’s AWS Foundational Security Best Practices (FSBP) standard is excellent at finding drift after something is already running, but teams that ship quickly need the same discipline earlier in the lifecycle. The practical move is to translate the controls you care about into policy-as-code and wire them into CI/CD security gates so insecure infrastructure never reaches a merge, plan approval, or deployment stage. This guide shows how to do that for CloudFormation and Terraform, with a prioritization strategy that turns the broad AWS Security Hub control set into specific pre-deploy checks. If you want a refresher on the underlying standard, start with the official AWS Foundational Security Best Practices standard and AWS’s broader guidance on being cited, not just ranked—the lesson for security is the same: controls only matter when they are implemented in the place decisions are made.
There’s also a pipeline-design lesson here. Good security automation behaves like a resilient data pipeline: it needs stable inputs, clear error handling, and feedback loops that don’t overload engineers. That is why many of the design patterns used in cloud pipeline tradeoff analysis and engineering metrics instrumentation map neatly to security controls. You are not trying to block every conceivable risk with one giant rule; you are prioritizing what is high impact, automatable, and cheap to fix before deployment. Done well, the result is fewer alerts from Security Hub later, lower remediation cost, and a much cleaner compliance story.
Why pre-deploy mapping matters more than post-deploy findings
Security Hub is a detector, not a gate by itself
AWS Security Hub FSBP continuously evaluates accounts and workloads against best practices, which is invaluable for visibility and audit readiness. But by the time a control fails in production, the organization has already paid the cost of deploying, observing, triaging, and remediating a bad state. In mature delivery systems, the first line of defense is not a dashboard; it is a gate that rejects unsafe infrastructure before it is merged or applied. Think of Security Hub as your runtime truth source, while CI/CD policy-as-code becomes the pre-flight checklist.
This distinction matters because many controls are deterministic at authoring time. If a Terraform module creates an S3 bucket without public access blocks, or a CloudFormation template launches EC2 instances with public IPs, those issues can be identified before a deployment ever occurs. Pre-deploy validation reduces security drift, lowers noise, and improves developer trust because failures show up next to the code change that caused them. That feedback loop is more actionable than a generic finding days later.
Translate controls into developer language
Engineers do better with exact, localized feedback than with abstract compliance labels. Instead of saying “FSBP failed,” your pipeline should say “S3 bucket lacks encryption” or “EC2 launch template enables public IP assignment.” That language maps directly to the resource block or parameter that needs modification, which shortens time-to-fix dramatically. This is the same reason teams build great developer experiences around linting rules: the rule is only useful if it tells the author exactly how to change the code.
When teams adopt this approach, Security Hub becomes the backstop instead of the first alarm. Your aim is not to duplicate AWS Security Hub line-by-line in CI, but to move the most consequential and automatable controls left. That creates a tiered model: pre-merge checks for static misconfigurations, pre-deploy gates for plan-time findings, and Security Hub for runtime drift and newly introduced service-side changes. This layered approach is significantly stronger than either runtime-only monitoring or all-or-nothing blocking.
The economics of failing early
Security fixes are cheapest when they are still text in a pull request. Once an issue lands in a shared environment, the blast radius includes logs, incidents, access reviews, and sometimes customer impact. In practice, that means controls tied to identity exposure, public network reachability, encryption, and logging deserve first-class enforcement at the pipeline level. A useful analogy comes from product and operations: teams that spot bad deals early, like in a buyer’s checklist for verifying deals or a deadline-deal playbook, avoid the downstream cost of regret. Security should work the same way.
How to prioritize FSBP controls for pre-merge checks
Start with controls that are static, high-impact, and common
Not every FSBP control belongs in pre-merge validation. Some controls require runtime behavior, AWS-managed service state, or contextual data that only exists after deployment. The best candidates for CI/CD gates are the ones that can be evaluated from templates, plans, or source code with high confidence. Prioritize controls that affect internet exposure, encryption, logging, identity, and metadata access because those are both high-risk and highly automatable.
A practical pre-merge shortlist often includes these patterns: public exposure on S3, RDS, EC2, ELB, and API Gateway; missing encryption at rest; missing access or execution logging; overly permissive IAM policies; missing IMDSv2; and resources that permit unauthenticated access. These are not obscure edge cases; they are the exact misconfigurations that recur across mature cloud environments. If you need a reminder that defaults matter, the same principle appears in consumer-tech risk reviews like default-sensitive device security analysis and ownership-risk spot checks.
Create three enforcement tiers
For most organizations, an effective setup has three layers. Tier 1 is the hard block: checks that fail the build on obvious violations, such as public S3 buckets, plaintext secrets, or disabled encryption where the service supports it by default. Tier 2 is a configurable warning or approval gate: findings like lack of flow logs or missing X-Ray, which may be required in production but not in every sandbox. Tier 3 is runtime-only: controls that need AWS service behavior, account-level settings, or ongoing observation after deployment.
That tiering keeps your policy usable. If you treat every issue as a hard failure, engineers will route around the system or disable it. If you only warn, the policy becomes theater. In security governance, the useful middle ground is to enforce the controls that are cheap to prove and expensive to ignore, then let Security Hub close the loop on the rest. This mirrors the operational thinking found in risk checklists for automation: guardrails must be specific enough to act on, or people won’t trust them.
Map controls to resource types, not just categories
FSBP categories are useful for organization, but resource-level mapping is what makes policy-as-code maintainable. For example, “logging should be enabled” means different things for API Gateway, Athena, and AppSync. A useful control map should identify the AWS resource, the validation source, the condition that constitutes failure, and the deployment stage where the check should run. Once you encode those dimensions, the policy becomes reusable across many repos and stacks.
This is where control mapping becomes an engineering artifact, not a spreadsheet. Treat it as a versioned document in the same repo as your IaC rules. That way, when AWS changes a service or Security Hub updates a control, the gate and the documented rationale evolve together. Teams that keep control logic close to code usually move faster than teams that manage security as static policy PDFs.
Control mapping table: what to gate before deploy
The table below shows a practical subset of FSBP controls and how to translate them into CI/CD validation. It is intentionally biased toward controls that are easy to evaluate from CloudFormation templates or Terraform plans. Use it as a starting point, then extend it for your own platform standards and exceptions.
| FSBP Control | What to check pre-deploy | CloudFormation example signal | Terraform example signal | Gate severity |
|---|---|---|---|---|
| Account.1 Security contact information | Account baseline documented in org pipeline, not template | Org bootstrap stack missing contact workflow reference | Workspace bootstrap missing account metadata output | Warn / platform gate |
| AutoScaling.3 IMDSv2 required | Launch template enforces metadata tokens required | MetadataOptions.HttpTokens != required | metadata_options.http_tokens != "required" | Hard fail |
| Autoscaling.5 No public IPs | Instances do not auto-assign public IPs | AssociatePublicIpAddress = true | associate_public_ip_address = true | Hard fail |
| APIGateway.4 WAF association | Public API stages protected by WAF | Missing WebACL association | No wafv2_web_acl_association | Warn or fail for prod |
| APIGateway.8 Authorization type specified | All routes/stages define auth explicitly | AuthorizationType absent/none | route_authorization_type missing | Hard fail |
| S3 public access protections | Block public access + no public ACL/policy | PublicAccessBlockConfiguration missing | aws_s3_bucket_public_access_block missing | Hard fail |
| Logging controls | CloudTrail / service logs enabled and retained | LoggingConfiguration missing | logging block missing | Warn or fail for prod |
| Encryption at rest | Default encryption enabled with approved KMS | BucketEncryption missing | server_side_encryption_configuration missing | Hard fail |
| Open security groups | No 0.0.0.0/0 on sensitive ports | Ingress allows broad CIDR | cidr_blocks includes 0.0.0.0/0 | Hard fail |
| IAM wildcards | Action/Resource scope limited | PolicyDocument has * on sensitive actions | policy json includes wildcards | Hard fail |
CloudFormation validation patterns that catch problems early
Use cfn-lint, taskcat, and custom rules together
CloudFormation validation should be layered. Start with syntax and schema validation using cfn-lint, then move into deployment-simulation checks with tools such as Taskcat, and finally add custom policy checks for your organization’s security requirements. The important point is that FSBP mapping should not be limited to one tool. A schema linter catches invalid constructs, while policy-as-code catches valid-but-insecure constructs, which is where most security debt lives.
A simple example is an S3 bucket. CloudFormation can be syntactically valid while still creating a public bucket, so your pre-merge gate should inspect the template for public access block configuration, bucket encryption, and any bucket policy allowing anonymous access. You can encode this as a custom rule in Python, YAML schema validation, or an Open Policy Agent policy that parses the template. This makes the failure mode precise: “S3 bucket MyDataBucket lacks server-side encryption” rather than “CloudFormation template invalid.”
Example CloudFormation guardrail
For teams using CloudFormation Guard, the rule below demonstrates how to enforce a common FSBP-aligned requirement: EC2 launch templates must require IMDSv2. That directly supports the intent of AutoScaling.3, which is one of the most valuable early gates because metadata theft is a frequent escalation path.
let lt = Resources.*[ Type == "AWS::EC2::LaunchTemplate" ]
rule imdsv2_required when %lt !empty {
%lt.Properties.LaunchTemplateData.MetadataOptions.HttpTokens == "required"
%lt.Properties.LaunchTemplateData.MetadataOptions.HttpEndpoint == "enabled"
}Note the structure: the rule is narrow, explicit, and testable. You can place it in a repository of shared guardrails and apply it across teams. If you need to review how security policies affect platform behavior more broadly, resources like budget gear tradeoff guides may seem unrelated, but the principle is the same: choose the smallest solution that still meets the target outcome. In security, that means one rule per failure mode, not one giant “secure everything” blob.
Detect public exposure in templates
To catch accidental exposure, scan for load balancers, API integrations, and security groups that open more than intended. CloudFormation-specific checks are especially useful because a single parameter default can create a wide-open stack across environments. For example, a default parameter like AllowPublicAccess=true might be acceptable in a dev sandbox but disastrous if reused in a staging or prod path. CI/CD should force explicit environment selection and require reviewers to acknowledge exposure-related parameters when they are non-default.
Where possible, fail on broad CIDRs in security groups, any route that lacks auth, and any resource that exposes a public endpoint without a compensating control such as WAF or restrictive IAM. These are simple conditions to express and they create a strong baseline. They also pair well with approval workflows, so you can require a security reviewer to sign off when a temporary exception is granted.
Terraform validation patterns that are actually maintainable
Use terraform validate, plan parsing, and policy engines
Terraform gives you more flexibility than CloudFormation, but that also means more ways to hide risk behind variables and modules. The standard stack is terraform validate for syntax, terraform plan -out for materialized intent, and a policy engine such as OPA/Conftest, Checkov, or Sentinel for security rules. The crucial move is to evaluate the plan, not just the HCL, because the plan reveals resolved values and provider behavior. That helps you catch cases where a module input defaults to insecure settings.
For example, an EC2 module might accept an associate_public_ip_address input that defaults to true. Terraform code review alone may miss the risk because the call site looks harmless. But a plan-level policy can inspect the actual evaluated resource and block the deployment if the final value exposes the instance publicly. This is the same kind of end-to-end validation that good analytics teams apply when they trace a metric from source event to dashboard, as described in payment analytics instrumentation patterns.
Example OPA/Rego policy for S3 encryption
Below is a concise Rego example that blocks S3 buckets without server-side encryption. This is one of the most straightforward FSBP-aligned gates to start with because it is cheap to evaluate and high value in production.
package terraform.security
default deny = []
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
after := resource.change.after
not after.server_side_encryption_configuration
msg := sprintf("S3 bucket %s must enable server-side encryption", [resource.name])
}Once you have a pattern like this, you can extend it to KMS key requirements, bucket public access blocks, and versioning. Keep each control surgical. If you try to encode every platform nuance in a single policy, you will create an unreadable rule set that is hard to maintain and harder to trust. Policy-as-code should be more like a well-factored library than a monolithic script.
Make module interfaces secure by default
The best Terraform security controls are often module design choices, not just external gates. Expose only secure defaults, make risky settings explicit, and require feature flags for exceptions. For instance, a module can default to private subnets, disabled public IPs, encrypted storage, and restricted security group rules. That reduces the number of failure states that CI has to catch in the first place. Security then becomes an architectural property rather than a post hoc quality check.
To keep the module contract clean, document the security assumptions right next to the variables. That makes peer review faster and exception handling more transparent. Teams building robust automation in other domains, such as clinical telemetry pipelines, often find the same truth: integration boundaries are where safety is won or lost.
Building a control-mapping matrix your team will actually use
Decide what blocks, what warns, and what audits
Control mapping becomes usable when every rule has an action. Create a matrix that classifies each FSBP control into one of three states: block merge, require approval, or monitor only. Put the decision in writing, including the environment scope and an owner for each control. Without this discipline, the policy set becomes an uncurated collection of rules that nobody can interpret under deadline pressure.
The strongest block candidates are the ones that expose data or remove core defenses: encryption off, public ingress open, IAM too broad, or auth omitted. Warning candidates are often observability and resilience controls such as logs, tracing, and multi-AZ coverage. Audit-only controls are those that depend on account-level state, third-party dependencies, or runtime behavior that cannot be inferred reliably from a plan. As with risk-stratified detection, the key is to match the intervention to the confidence level.
Make exceptions time-bound and reviewable
There will always be legitimate exceptions. A temporary public endpoint for a partner integration, a test bucket without production data, or a legacy stack awaiting refactoring may need a carve-out. The danger is when exceptions become permanent and invisible. Require a ticket, an owner, a reason, an expiry date, and a linked compensating control for every waiver.
In practice, this means your pipeline should support annotated overrides rather than hidden bypasses. A security exception should be visible in code review, in the deployment logs, and in your compliance reporting. That approach mirrors good governance in operational systems where change must be attributable and reversible. If you need a consumer-facing parallel, think of how careful comparison shoppers validate promotional claims before spending; the same discipline applies to security exceptions.
Track false positives and policy debt
Every security gate generates friction, and friction that is not measured becomes resentment. Track how often rules are overridden, how often they are fixed immediately, and how long they remain open. If one FSBP-aligned rule triggers constant exceptions, it may be poorly scoped, misconfigured for your environment, or missing contextual inputs such as environment tags. This is where the metrics mindset from engineering SLO practice pays off: if you do not measure the gate, you cannot improve it.
Recommended starter sequence for CI/CD rollout
Phase 1: Highest-confidence controls
Begin with the controls most likely to succeed on day one. These usually include S3 encryption, public access blocks, IAM wildcard detection, security group exposure, and IMDSv2 enforcement. They are easy to explain, easy to test, and hard to dispute. This early success builds trust in the process and proves the value of security gates without overwhelming the delivery teams.
Phase 2: Service-specific controls
Next, add service-specific rules for API Gateway auth, WAF association, logging, and tracing, plus any database or cache controls relevant to your platform. These checks often require better parsing of plans or templates, but they produce a strong security uplift because they protect public-facing workloads. If your organization uses multiple teams and stacks, this phase is also where shared rulesets pay dividends. A single centrally managed policy repo can prevent dozens of teams from recreating the same mistakes.
Phase 3: Environment-aware approvals and audits
Finally, extend the system with environment-aware governance. A development stack may tolerate weaker logging, but production should not. A temporary exception in a sandbox may be acceptable if it expires automatically. Use approvals, tags, and deployment environments to encode those distinctions so you do not force one policy on every stage. Good governance, like good product discovery, is contextual rather than absolutist.
Operational best practices for keeping the gates useful
Document the “why” with each rule
Every control should have a short rationale that explains the risk, the security objective, and the remediation. Engineers are far more likely to comply if they understand what the rule protects and how to fix it. That documentation also helps future maintainers when AWS changes service behavior or you upgrade your policy engine. Treat the rationale as part of the control, not an afterthought.
Version policies with application code
Put policy-as-code in version control, peer review it, and release it deliberately. This avoids the common anti-pattern where a security team edits an external policy set that application teams discover only after it breaks their deploy. When policy changes are code-reviewed like any other change, they get better tests and fewer surprises. This is also the best way to keep CloudFormation and Terraform validation aligned with the realities of each service team.
Continuously reconcile with Security Hub
Finally, compare your pre-deploy gates with runtime Security Hub findings. If Security Hub flags an issue that CI never caught, either the rule should be added leftward, or the runtime control may not be derivable from the source artifact and should remain a detector. This reconciliation loop is essential because cloud services evolve, and what was runtime-only yesterday may be statically checkable tomorrow. Organizations that keep tuning this feedback cycle are the ones that maintain both velocity and assurance.
Pro tip: start with 10 controls, not 100. The goal is not comprehensive enforcement on day one; the goal is a trusted system that blocks the right mistakes and teaches developers how to avoid them next time.
Practical examples: what a successful gate looks like
Example 1: API Gateway without auth
A developer opens a pull request adding a new API Gateway route. The plan shows the route exists, but the authorization type is missing. Your OPA or Guard rule fails the pipeline with a message that points directly to the resource and the required setting. The developer adds Cognito, IAM, or Lambda auth, the gate passes, and Security Hub never needs to complain about the deployed endpoint.
Example 2: EC2 in Auto Scaling with public IPs
A platform team adds a launch template for an application tier. The Terraform plan reveals that instances will receive public IPs by default. The gate blocks the merge because that setting conflicts with your baseline. The team fixes the module input, and from then on every consumer of the module inherits the safer behavior.
Example 3: S3 bucket missing encryption
An analyst team provisions a bucket for report exports. The pre-deploy check detects no server-side encryption configuration and no public access block. The fix is straightforward: add encryption, block public access, and ideally enforce a KMS key policy aligned with least privilege. Once the module default is corrected, that protection scales across all future buckets.
Frequently asked questions
Which AWS Security Hub FSBP controls should I enforce first?
Start with controls that are static, high-impact, and common: encryption at rest, public access exposure, IAM wildcards, IMDSv2, and missing authorization on public endpoints. These are easy to map into CloudFormation and Terraform checks, and they eliminate a large share of avoidable risk quickly.
Should every FSBP control become a hard CI failure?
No. Some controls are better as warnings, approvals, or runtime-only detections. Hard-fail the controls that are unambiguous and dangerous, but avoid turning the pipeline into a blocker for issues that need environment context or runtime validation.
What’s the best way to validate Terraform against FSBP?
Use a combination of terraform validate, plan parsing, and policy engines like OPA/Conftest, Checkov, or Sentinel. The plan is especially important because it shows the resolved resource values that determine whether a deployment is actually secure.
How do I handle exceptions without weakening the whole program?
Require time-bound, documented exceptions with an owner, reason, expiry date, and compensating control. Keep them visible in code review and deployment logs so exceptions remain auditable rather than becoming silent bypasses.
How do I keep Security Hub and CI/CD policies from drifting apart?
Reconcile runtime findings with pre-deploy failures on a regular cadence. If Security Hub catches a recurring issue that CI missed, add or refine the static gate. If a control cannot be checked statically, keep it as a runtime detector and document that limitation.
Can I use the same rules for dev, staging, and production?
Not always. Environment-aware policy is usually safer and more practical. Production should enforce the strictest baseline, while dev and test can use scoped exceptions if they are documented and time-bound.
Related Reading
- Plugging Chatbots: How Risk-Stratified Misinformation Detection Can Stop Dangerous Health and Security Recommendations - A useful framework for matching guardrail strength to risk level.
- Payment Analytics for Engineering Teams: Metrics, Instrumentation, and SLOs - Strong ideas for measuring gate quality and policy drift.
- Prompt Linting Rules Every Dev Team Should Enforce - A practical model for turning abstract standards into precise, developer-friendly checks.
- Low-latency market data pipelines on cloud: cost vs performance tradeoffs for modern trading systems - Helpful for thinking about speed, reliability, and tradeoffs in cloud automation.
- Anti-Stalking Tech Is Only as Good as Its Defaults: What AirTag 2’s Update Really Changes - A reminder that secure defaults are the foundation of any trustworthy system.
Related Topics
Daniel Mercer
Senior Cloud Security Editor
Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.
Up Next
More stories handpicked for you
Plain Language Rules vs. DSL: Writing Effective Code Review Policies for Kodus
Self-Hosted Code Review Agents: Integrating Kodus into Your CI Without Vendor Lock-In
From PCB Specs to Firmware Tests: Simulating EV Electronic Subsystems for Dev Teams
From Our Network
Trending stories across our publication group