Cloud Native Security Posture

Production engineering guide for cloud native security posture covering patterns, implementation strategies, and operational best practices.

Cloud native security posture refers to the state of an organisation’s cloud infrastructure, workloads, and processes as measured against a defined set of security controls, policies, and observability signals. It matters when teams need to validate that their cloud environments are consistently secure across multiple accounts, regions, and services—especially during continuous deployment, incident response, or audit cycles. A strong posture enables teams to detect drift, enforce compliance, and respond to threats with confidence, regardless of which cloud platform or tooling stack they use.

Establishing Security Baseline Across Environments

To establish a security baseline, teams define a standard set of security configurations across all cloud resources. This includes identity, network, storage, and compute layers. The process begins with a declarative policy language applied uniformly across environments.


apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: enforce-secure-pod-configuration
spec:
  rules:
    - name: ensure-secure-pod-configuration
      match:
        resources:
          kinds:
            - Pod
      validate:
        message: |
          Pod must have security context, resource limits, and at least one init container.
        pattern:
          spec:
            securityContext:
              runAsNonRoot: true
              fsGroup: 1000
              supplementalGroups: [1000]
            containers:
              - name: "*"
                resources:
                  limits:
                    memory: "1Gi"
                    cpu: "1000m"
                  requests:
                    memory: "512Mi"
                    cpu: "250m"
            initContainers: "?*"

This policy is applied via kubectl apply -f security-baseline.yaml to all clusters. A failure is signaled when a Pod lacks required fields, and Kyverno reports:

{
  "result": "failed",
  "message": "Pod myapp-7f5c8d9b45 must have security context, resource limits, and at least one init container.",
  "details": [
    {
      "field": "spec.containers[0].resources.limits.memory",
      "expected": "1Gi",
      "actual": "512Mi"
    }
  ]
}

The baseline is enforced through CI/CD pipelines using kyverno apply as a pre-deployment step. If a new Pod manifest is merged into main, the pipeline runs:

kyverno apply --cluster-policy security-baseline.yaml --input-pod-templates/ --dry-run

A missing runAsNonRoot: true results in a failed validation and blocks the merge until fixed.

Detecting and Remediating Drift in Configuration

Configuration drift occurs when environments diverge from the baseline. Detection relies on periodic audits using tools that compare actual state against desired state.

A common approach uses terraform plan in a CI job to detect drift:

terraform plan -out=tfplan -var-file=prod.tfvars

This generates a tfplan file that includes changes. The output includes a Plan section with diffs:

Plan: 2 to add, 3 to change, 1 to destroy

# aws_security_group.app_sg
+ resource "aws_security_group" "app_sg" {
    id = "sg-1a2b3c4d"
    name = "app-sg"
    description = "Application security group"
    egress {
        cidr_blocks = ["0.0.0.0/0"]
        from_port = 80
        protocol = "tcp"
        to_port = 80
    }
    ingress {
        cidr_blocks = ["10.0.0.0/16"]
        from_port = 443
        protocol = "tcp"
        to_port = 443
    }
    # new rule: allow health checks on port 8080
    + ingress {
        cidr_blocks = ["10.0.0.0/16"]
        from_port = 8080
        protocol = "tcp"
        to_port = 8080
    }
}

The ingress block shows a new rule that was not in the previous state—indicating drift.

To remediate, a job runs terraform apply -input=false -auto-apply tfplan, which applies changes without user interaction. A common failure mode is missing input = false, causing the job to wait for manual input, which blocks CI/CD.

Another drift detection method uses git diff on a set of configuration files stored in config/:

git diff --name-only HEAD config/

If any file in config/ has changed, the pipeline triggers a drift analysis. The output includes a list of modified files:

config/aws/iam/roles/app-role.yaml
config/kubernetes/ingress/production.yaml
config/database/production.yaml

A tool like yq parses these files to extract changes:

yq '.spec.securityContext.runAsNonRoot' config/kubernetes/ingress/production.yaml

A value of false instead of true is silently ignored if yq is not versioned in CI, leading to incorrect security context evaluations.

Enforcing Identity and Access Management Policies

Identity and access management (IAM) policies must be consistent across cloud providers. Policies are defined in JSON or YAML and validated using a central policy engine.

Example IAM policy in JSON:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::myapp-prod/*"
    },
    {
      "Effect": "Deny",
      "Action": "s3:DeleteObject",
      "Resource": "arn:aws:s3:::myapp-prod/backup/*",
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpc": ["vpc-12345678"]
        }
      }
    }
  ]
}

This policy is applied to an AWS IAM role using aws iam put-role-policy. A failure occurs when Condition is misspelled as Conditions or when StringNotEquals is nested incorrectly.

Validation is performed using aws iam simulate-principal-policy:

aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::123456789012:role/app-deployer \
  --action-names s3:GetObject s3:PutObject \
  --resource-arns arn:aws:s3:::myapp-prod/data.json \
  --output json

The response includes a EvaluationResults array:

[
  {
    "EvalActionName": "s3:GetObject",
    "EvalResourceName": "arn:aws:s3:::myapp-prod/data.json",
    "MatchedStatements": [
      {
        "Sid": "allow-read-write",
        "Effect": "Allow",
        "Action": "s3:GetObject",
        "Resource": "arn:aws:s3:::myapp-prod/*"
      }
    ],
    "MissingContextValues": [
      "aws:SourceVpc"
    ]
  }
]

The MissingContextValues field reveals that aws:SourceVpc was not provided in the request, causing the Deny rule to fail. This is a common silent failure: the policy applies but doesn’t enforce the VPC restriction.

To enforce policy consistency, a CI job runs:

aws iam list-attached-role-policies --role-name app-deployer

and compares the output against a reference list. A difference in the list—such as an extra arn:aws:iam::123456789012:policy/backup-manager—triggers a drift alert.

Monitoring Runtime Security with Observability Signals

Runtime security posture depends on continuous monitoring of running workloads. Key signals include container image integrity, network policy enforcement, and runtime anomalies.

A docker inspect command reveals a container’s configuration:

docker inspect myapp:v1.2.3

The output includes a Mounts section:

"Mounts": [
  {
    "Type": "bind",
    "Source": "/etc/app/config.yaml",
    "Destination": "/app/config.yaml",
    "Mode": "",
    "RW": true,
    "Propagation": "rprivate"
  }
]

A missing RW: true field causes the container to fail writing logs, but this is often unnoticed unless explicitly checked.

Security signals are collected via syslog and journalctl on nodes:

journalctl --since="2023-10-05T00:00:00" --unit=containerd --output=short-precise

Output includes:

Oct 05 08:23:15 app-node-1 containerd[1234]: [INFO] container created: myapp-7f5c8d9b45
Oct 05 08:23:16 app-node-1 containerd[1234]: [ERROR] failed to mount volume: path /data not found
Oct 05 08:23:16 app-node-1 containerd[1234]: [WARNING] no health check configured for myapp

The ERROR log line indicates a volume mount failure, while the WARNING suggests a common oversight: health checks are not configured in the container image, but are expected by the orchestrator.

To validate image integrity, a pipeline uses cosign to verify signed container images:

cosign verify --certificate-oidc-claim=subject --keyring=cosign.keys myapp:v1.2.3

The command returns:

{
  "subject": "[email protected]",
  "signature": "sha256:abc123...",
  "image": "myapp:v1.2.3",
  "verified": true,
  "annotations": {
    "org.opencontainers.image.created": "2023-10-04T12:00:00Z"
  }
}

A failure occurs when --certificate-oidc-claim=subject is misspelled as --certificate-oidc-claim=subject-name, causing the OIDC claim lookup to fail silently.

Responding to Security Incidents with Standardized Workflows

When a security incident occurs—such as a compromised container or unexpected network traffic—teams follow a standardized response workflow.

An incident starts with a curl request to a central alerting service:

curl -X POST https://alerting.example.com/api/v1/incidents \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Unauthorized access to production database",
    "severity": "critical",
    "source": "cloud-native-security",
    "details": {
      "event": "cloud-native-security:db-access",
      "timestamp": "2023-10-05T08:23:15Z",
      "host": "db-prod-1",
      "user": "backup-user",
      "action": "CREATE TABLE"
    }
  }'

The service returns a 201 Created response with a location header:

HTTP/1.1 201 Created
Location: /incidents/abc123
Content-Type: application/json

{
  "id": "abc123",
  "status": "open",
  "created_at": "2023-10-05T08:23:15Z"
}

An incident response script then runs:

# incident-response.sh
incident_id="$1"
curl -X PATCH https://alerting.example.com/api/v1/incidents/$incident_id \
  -H "Content-Type: application/json" \
  -d '{"status": "in-progress", "assigned_to": "security-team"}'

A common failure is a missing Content-Type header in the PATCH request, causing the service to treat the body as text/plain, which leads to a 400 Bad Request with:

{
  "error": "invalid request body",
  "details": "expected JSON, got text/plain"
}

The PATCH request is often sent without a body field, leading to a partially updated incident.

For incident investigation, teams query a central logging service using a pg_dump-style format:

psql -h logs.example.com -U loguser -d logs -c '
  SELECT
    timestamp,
    message,
    level,
    source
  FROM events
  WHERE
    source = 'containerd'
    AND level = 'ERROR'
    AND timestamp > '2023-10-05T08:00:00'
  ORDER BY timestamp DESC
  LIMIT 50
'

The result includes rows with timestamp, message, level, and source. A missing source = 'containerd' filter causes the query to include logs from unrelated systems, making it harder to identify root causes.

Maintaining Posture Through Automation and Feedback

Maintenance of cloud native security posture relies on automated feedback loops. A nightly job runs kubectl get all -A -o yaml and compares the output against a baseline snapshot stored in baseline/.

kubectl get all -A -o yaml > current-state.yaml
diff -u baseline/production.yaml current-state.yaml

The diff output shows changes:

--- baseline/production.yaml	2023-10-04
+++ current-state.yaml	2023-10-05
@@ -1,7 +1,7 @@
 apiVersion: v1
 kind: Namespace
 metadata:
-  name: staging
+  name: production
   labels:
     environment: staging
+  annotations:
+    team: backend

A change in name: staging to production is flagged as a configuration drift. If the annotations field is missing, the change is not captured, leading to undetected drift.

Feedback is delivered via git commit and git push to a security-posture repository:

git add .
git commit -m "Security posture updated: detected drift in production namespace"
git push origin main

A common failure is forgetting to git add the baseline/ directory, causing the diff to be applied to a stale baseline. A missing git push results in a detached HEAD state, with changes not visible in the main branch.

These feedback loops ensure that the security posture is not a static document, but a living, evolving system that adapts to changes in the cloud environment.

This page was rewritten on 16 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.