Integrating DevSecOps with IaC for Security | Hokstad Consulting

Integrating DevSecOps with IaC for Security

Integrating DevSecOps with IaC for Security

If security is not built into your IaC pipeline, you are pushing risk with every deployment. The article’s main point is simple: put security into your Terraform, modules, policies, state handling, approvals, and drift checks from day one. That cuts late fixes, limits misconfigurations, and gives you a clear audit trail.

I’d boil it down to this:

  • Build secure defaults into modules so teams do not start with open access, public IPs, or broad IAM
  • Keep secrets and state locked down with encryption, access limits, audit logs, and separate environments
  • Run checks at every step: IDE, pre-commit, pull request, CI, plan, and apply
  • Use policy as code to block changes that break your rules before they hit production
  • Watch for drift after deployment and auto-fix low-risk issues where approved
  • Set clear ownership and approval paths for high-risk changes like IAM, network rules, and key management

One data point stands out: teams that built security into development and CI/CD saw a 70% median drop in cloud misconfiguration. That is the case for shifting checks left and keeping them in place after deployment.

Here’s the article in one view:

Area What I’d focus on Risk reduced
IaC design Secure-by-default modules, least privilege, approved regions Bad defaults in new deployments
Secrets and state No secrets in code, encrypted remote state, tight access Credential leaks and state exposure
Pipeline checks Linting, secret scans, plan review, policy gates Misconfigurations reaching live environments
Live control Drift detection, audit logs, GitOps reconciliation Manual changes and hidden config drift
Governance RACI, separation of duties, break-glass logging Weak approvals and policy bypass

Bottom line: treat infrastructure code, security rules, and delivery pipelines as one system, and you get more control without slowing delivery to a crawl.

::: @figure DevSecOps IaC Security Pipeline: Checks at Every Stage{DevSecOps IaC Security Pipeline: Checks at Every Stage} :::

Security of IaC Pipelines and Infrastructure Governance With Policies-As-Code | PulumiUP 2024

2. Build secure IaC foundations before deployment

Security in IaC starts before anything goes live. Snyk found a 70% median drop in cloud misconfiguration when teams built security into development and CI/CD.[5] That’s the big reason secure defaults in reusable modules matter so much: they cut down on manual clean-up later.

Threat modelling, least privilege and secure defaults

Begin by mapping the parts of your infrastructure that matter most: assets, entry points and trust boundaries. For instance, look at the path from the internet to a load balancer, then into an application subnet and on to a data subnet. For higher-risk workloads, lightweight methods such as STRIDE or attack-path mapping are often enough to spot where things could go wrong. The OWASP IaC Security Cheat Sheet treats threat modelling and least privilege as foundational controls.[4]

Then turn those findings into module defaults. In plain terms, don’t make people remember the safe option every time. Build it in:

  • No public IPs
  • No inbound rules by default
  • Scoped IAM
  • Approved regions only
  • Logging switched on

If someone needs an exception, make them ask for it clearly.

Secrets, remote state and environment separation

Secrets - API keys, database passwords and certificates - should never sit in IaC source files or end up in version control. Instead, reference secret IDs in templates and fetch the values at runtime through workload identities, not static keys. Rotate secrets every 30 to 90 days and audit access from one central place.

Remote state also needs careful handling. It’s a high-value target, so store it with encryption, access controls and audit logs. Access should be limited to the build system and a small admin group.[2][3] In practice, that means server-side encryption with KMS-managed keys, TLS in transit, state locking such as DynamoDB locks for S3 backends, and separate state backends for each environment. If dev state is compromised, it should not open a path into production.

Environment separation backs this up. Use separate accounts, subscriptions or projects for development and production, with access controlled through service control policies.[1] That gives production a much tighter access boundary and keeps backup, logging and data residency controls neatly scoped.

Secure configuration patterns that scale

Reusable module patterns make this work at scale. A hub-and-spoke network, for example, lets you centralise inspection and egress control while keeping application environments in isolated spokes. Encryption should default to provider-managed keys for storage and databases, with customer-managed keys where policy or regulation calls for them. TLS should be enforced for service-to-service traffic.

Tags matter too. Mandatory tags such as Environment, Owner, CostCentre and DataClassification tie infrastructure back to teams, budgets and risk groups. Standard IAM roles like AppRuntime, Admin and ReadOnly should map to job functions, not personal preference.

Publish these patterns as approved modules, and let the pipeline enforce their use. That way, the module interface becomes the place where IDE, pre-commit and CI checks apply in the next stage.

3. Embed security checks across the DevSecOps pipeline

Once approved modules are in place, the next step is to apply the same checks at each hand-off: edit, commit, PR, plan and apply. The goal is simple. Catch problems early, keep day-to-day work moving, and save hard blocks for changes that carry more risk.

Checks at the IDE, pre-commit and pull request stages

The first line of defence should be light and fast. In the IDE, tools such as TFLint and Checkov can flag IaC mistakes, missing encryption at rest and plain misconfigurations while a developer is still typing. At the same stage, Gitleaks or TruffleHog can scan for plaintext secrets before anything gets committed.[8][10][12]

That matters because local checks can stop common issues from drifting into a shared branch in the first place, including hard-coded AWS keys, open security groups, and missing or wrong tags. Fixing those at source is far less painful than dealing with them later in CI.

Pre-commit hooks apply the same checks automatically on every git commit, so the process does not depend on each person remembering to run tools by hand. When a platform team sets these hooks up in a shared repository and rolls them out across teams, the rules stay consistent across the engineering organisation instead of changing from one laptop to the next.

At the pull request stage, checks should tighten up. Full IaC scans run in CI with tfsec or Checkov. Reviewers work from a fixed checklist covering least-privilege IAM, correct environment scoping and TLS on all endpoints. The proposed Terraform plan should also be attached to the PR, so reviewers can see the exact change rather than guessing from the source alone.

For higher-risk PRs, the approval bar should go up as well. If a change touches network perimeters, identity, or key management, a security or platform lead should be a required approver, not an optional extra. That keeps security review inside the same path as code review instead of turning it into a side process.

Once a PR is clean, the plan still needs to clear policy checks before apply.

CI validation and pre-apply policy gates

After a PR merges, CI takes over. The pipeline generates an infrastructure plan and sends it through policy-as-code engines such as Open Policy Agent (OPA), Conftest or Sentinel. These tools check the actual diff, not just the source templates, against codified organisational rules. That might mean no internet-facing load balancer without HTTPS, all storage encrypted with managed keys, and production resources limited to UK or EU regions.[7]

A gate lives or dies by its feedback. If a change is blocked, the engineer should be told exactly what failed, which policy was broken, and how to fix it. For example:

Azure Storage account 'prod-logs' is not encrypted at rest; enable encryption to meet ISO 27001 A.10.1 requirements.[11]

Vague failures annoy people and slow teams down. Clear, fix-focused messages are much easier to act on. It also helps to separate soft gates from hard gates. A soft gate warns in lower environments without blocking the change. A hard gate stops the change in staging and production.[6][11] That split keeps the pipeline from turning into a brick wall on every small edit.

Workflow choices for scanning, policy, secrets and state

Layered controls tend to work best when each stage has a clear owner and a clear failure mode.

Workflow element Lifecycle stage Owner Main risk reduced
Local IaC linting and secret scanning (IDE, pre-commit) Developer workstation Developers / platform Insecure configs and plaintext secrets entering the repo; rework in CI/CD
CI-based IaC scanners (tfsec, Checkov) On push / pull request Platform / DevOps Misconfigurations reaching shared branches; inconsistent enforcement
Policy-as-code engines (OPA, Sentinel, Conftest) Plan evaluation, pre-apply Security + platform Non-compliant changes being applied to environments; deviations from UK regulatory controls
Secret references and retrieval All stages - dev, CI, prod Security + platform Plaintext secrets in code, variables or logs
Remote state access Plan/apply, ongoing operations Platform / DevOps Unauthorised access, tampering and state corruption

CI should run with short-lived federated identities rather than long-lived keys, and production applies should be limited to a tightly audited automation account.[9][12] If the pipeline is ever compromised, that keeps the blast radius smaller.

4. Enforce policy, detect drift and automate remediation

Passing a pre-apply gate isn't the end of the story. Infrastructure can still change after deployment through manual edits, emergency fixes, or out-of-band automation. Once code clears the pipeline, live controls need to keep it in line. Use policy, drift detection and remediation to keep deployed infrastructure aligned with code.

Policy as code for security and compliance

Policy as code keeps security and compliance rules in version control, where teams can version, review and test them. It turns security rules into runtime controls that carry DevSecOps beyond deployment and into day-to-day operations. Common rules cover public access, encryption, approved regions and least-privilege IAM. These rules should map directly to internal standards or formal requirements. For example, an encryption rule can tie back to your data protection baseline, while least-privilege rules can map to identity governance policies. That way, a violation has clear compliance context instead of looking like a vague failure.

Policy definitions need testing before enforcement. In practice, that means:

  • writing unit tests for rule logic
  • running fixture-based tests against known bad configurations
  • including regression tests for approved exceptions that must stay allowed

Merge policy changes only after unit, fixture and regression tests pass.

Exceptions will happen, but they need guardrails. Temporary waivers should be resource-specific, ticketed, owned, time-boxed and auditable. Broad exemptions with no end date are often where compliance posture starts to slip. The audit trail should show who approved the exception, the risk accepted, and when the waiver will be reviewed or removed.

Policy needs live enforcement, not just merge-time approval.

Continuous monitoring, drift detection and automated fixes

Use audit logs, scheduled plans and GitOps reconciliation to catch drift before it turns into a breach. Configuration drift is any gap between the desired state in Git and the actual state running in your environment. The most security-sensitive examples include manual firewall changes, IAM drift, disabled logging or direct console edits. Not all drift has the same risk level. A cosmetic tag change is low priority. A privilege escalation or suppressed audit log is an urgent alert.

Good detection uses more than one layer. Scheduled terraform plan runs across workspaces compare live resources against state files and surface unexpected differences. Cloud audit logs - AWS CloudTrail, Azure Activity Logs and GCP Cloud Audit Logs - catch out-of-band changes as they happen and send them into monitoring pipelines. For Kubernetes workloads, GitOps controllers such as Argo CD and Flux CD run a watch, compare and reconcile loop. Use automatic reconciliation in non-production. In production, require approval before reconciliation.

Only automate fixes that are low-risk, reversible and pre-approved. An event-driven function triggered by an audit event can automatically revert an open security group to a secure baseline soon after the change is made. For higher-risk drift - credential rotation, removal of public exposure, or termination of workloads - send the issue to a manual review workflow while still giving teams automated fix guidance to speed up the decision. Automate low-risk fixes; send high-risk drift to manual review.

Drift severity Example Recommended response
Low Missing resource tag, cosmetic naming change Log and report; no block required
Medium Non-approved region deployment, missing logging config Alert owner; remediate within agreed SLA
High Open security group (0.0.0.0/0), disabled encryption Auto-remediate or quarantine; raise incident
Critical IAM privilege escalation, audit log suppression Immediate alert; manual review; incident response

Track blocked violations, mean time to detect drift, mean time to remediate critical deviations and repeat violations. Repeat issues usually point to a gap in developer guidance or a policy that's too easy to work around. Those problems are better fixed at the source than handled again and again with automated clean-up. The next section turns these controls into ownership and approval flow.

5. Operating model, governance and conclusion

Once controls are in place, governance is what makes them stick.

Ownership, approval flow and separation of duties

Controls fall apart when no one owns them. Keep ownership plain:

  • Developers own application IaC
  • Platform engineers own shared services
  • Security owns policy and risk acceptance
  • Operations owns runbooks and production approval

Security should keep veto rights on high-risk changes. That sets the approval flow for everything else in this section.

Approval flow should match risk. It shouldn't be one rule for every change. Standard changes can go through peer-reviewed pull requests and automated checks. High-risk changes need security review, especially changes to VPC peering, broad IAM roles, or firewall rules.

For sensitive IaC, split authorship, approval, and production access. That's a simple way to cut down the chance of mistakes or workarounds. CI/CD pipelines should sit with platform or security teams, so engineers can't dodge policy checks by changing pipeline definitions. Direct production applies outside the pipeline should be blocked, apart from tightly controlled break-glass access. When that access is used, every action needs to be logged to a central audit trail.

When to bring in specialist support

This model only holds up if the team can keep it running. If the same findings keep coming back, or new regulatory, scale, or delivery demands start to outstrip the team's capacity, it's time to get outside help.

Hokstad Consulting can help design secure DevSecOps workflows, standardise IaC modules, and implement policy as code without expanding team size.

Key takeaways

This operating model works only if the controls are used the same way every time. That means secure defaults, centralised secrets and state, pipeline checks, policy as code, drift monitoring, and clear ownership.

A practical starting point is:

  • Define a Cloud and IaC Security Standard
  • Add policy checks
  • Centralise secrets and state
  • Document a simple RACI and approval flow

None of this calls for a large team or heavy process. It calls for clarity, consistency, and the discipline to treat infrastructure code with the same care as application code.

FAQs

How do we secure Terraform quickly?

Shift security left by adding automated scanning tools like Checkov, tfsec, or KICS to your development workflow and CI/CD pipelines. That way, you can spot problems early, not after the code has already moved down the line.

Pre-commit hooks help too. They can catch hard-coded secrets and configuration mistakes before anything gets committed, which saves a lot of pain later.

Enforce standards with Policy-as-Code frameworks such as Open Policy Agent, and start in audit mode first. That gives teams room to see what’s being flagged before rules begin blocking work.

It also helps to strip sensitive credentials out of templates altogether. Instead, fetch them securely at runtime from encrypted vaults.

What changes should always need manual approval?

Manual approval should be required for production deployments and configuration changes with broad impact, especially when they affect costs or sensitive security settings.

These reviews serve as a final safeguard. They add human judgement around business logic, architectural fit, and domain-specific details that automated policy-as-code tools can't assess.

How often should we check for infrastructure drift?

Manual changes can push systems out of compliance within hours. That’s why periodic audits often aren’t enough. Continuous monitoring gives you a way to spot issues as they happen, not days or weeks later.

Tools such as Chef InSpec and Azure Policy can detect configuration drift in real time. In plain English, they flag when your live infrastructure starts to stray from your Infrastructure as Code templates. That helps teams stay aligned and cut security risks before they turn into bigger problems.