Date
September 9, 2025
Author
Karan Patel
,
CEO

Modern application development has shifted dramatically. Containers, microservices, Kubernetes, and serverless functions have replaced monolithic architectures, and with that shift comes an entirely new attack surface. Traditional perimeter-based security simply does not translate to cloud-native environments. If you are building applications on platforms like AWS, GCP, or Azure using containers and orchestration tools, security cannot be an afterthought bolted on at the end of the pipeline. It has to be woven into every layer, from your Dockerfile to your CI/CD pipeline to your runtime environment.

This post walks through the core cloud-native security practices that engineering and security teams should implement when building applications from the ground up. Each section includes practical, production-ready techniques you can apply immediately.

What Cloud-Native Security Actually Means

Cloud-native security is not just about scanning containers for vulnerabilities. It is a holistic approach that covers the full application lifecycle, from code commit to production runtime. The Cloud Native Computing Foundation (CNCF) frames this as the "4Cs of Cloud Native Security": Code, Container, Cluster, and Cloud.

Each layer depends on the security of the layer beneath it. A hardened container running insecure application code is still insecure. A well-written app deployed on a misconfigured Kubernetes cluster is still at risk. Security has to be addressed at every level simultaneously.

Shift-Left Security: Securing Code Before It Ships

"Shift left" is the practice of moving security testing earlier in the development lifecycle. Instead of running security scans after a build is complete, you integrate them into the developer workflow.

Static Application Security Testing in CI Pipelines

Tools like Semgrep and Bandit (for Python) allow you to scan source code for insecure patterns before a single line hits production.

Here is a Semgrep config targeting common cloud-native misconfigurations in Python:

rules:
 - id: hardcoded-aws-secret
   patterns:
     - pattern: |
         $VAR = "AKIA..."
   message: "Hardcoded AWS access key detected"
   languages: [python]
   severity: ERROR

 - id: insecure-http-request
   patterns:
     - pattern: requests.get(..., verify=False, ...)
   message: "SSL verification disabled in HTTP request"
   languages: [python]
   severity: WARNING

[cta]

You can run this against your codebase with:

semgrep --config ./semgrep-rules.yaml ./src/

[cta]

If you want to go deeper into integrating static analysis and other offensive security testing techniques into your workflow, the structured courses at Redfox Cybersecurity Academy give you hands-on labs built around real pipelines.

Secrets Scanning with Gitleaks

Hardcoded credentials in source code remain one of the most common causes of cloud breaches. Gitleaks scans your Git history for exposed secrets, including API keys, tokens, and passwords.

gitleaks detect --source . --report-format json --report-path gitleaks-report.json

[cta]

To run it automatically on every commit, add it as a pre-commit hook:

# .pre-commit-config.yaml
repos:
 - repo: https://github.com/gitleaks/gitleaks
   rev: v8.18.2
   hooks:
     - id: gitleaks

[cta]

Container Security: Hardening Your Images and Runtime

Containers are the fundamental unit of cloud-native deployments, and they introduce a distinct set of security considerations. A vulnerable base image, an overprivileged container, or a writable filesystem can each become an entry point for attackers.

Writing Secure Dockerfiles

Most container vulnerabilities originate from poor Dockerfile practices. Here is a hardened Dockerfile example for a Node.js application:

# Use a minimal, pinned base image
FROM node:20.11.1-alpine3.19

# Create a non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copy only what is needed
COPY package*.json ./
RUN npm ci --only=production

COPY --chown=appuser:appgroup . .

# Drop to non-root
USER appuser

# Explicitly expose the port
EXPOSE 3000

# Use exec form to avoid shell injection
CMD ["node", "server.js"]

[cta]

Key practices in this Dockerfile include using a pinned, minimal base image to reduce the attack surface, running as a non-root user, copying only production dependencies, and using exec form for the CMD instruction to prevent shell injection.

Scanning Container Images with Trivy

Trivy by Aqua Security is the industry-standard tool for scanning container images, filesystems, and IaC configs for vulnerabilities and misconfigurations.

# Scan a local image
trivy image --severity HIGH,CRITICAL myapp:latest

# Scan and fail the build if critical CVEs are found
trivy image --exit-code 1 --severity CRITICAL myapp:latest

# Output results as JSON for pipeline integration
trivy image --format json --output trivy-results.json myapp:latest

[cta]

Integrate this into your CI pipeline (GitHub Actions example):

- name: Run Trivy vulnerability scanner
 uses: aquasecurity/trivy-action@master
 with:
   image-ref: 'myapp:${{ github.sha }}'
   format: 'sarif'
   output: 'trivy-results.sarif'
   severity: 'CRITICAL,HIGH'
   exit-code: '1'

[cta]

Runtime Security with Falco

Trivy catches vulnerabilities at build time. Falco, a CNCF project, monitors your containers at runtime and alerts you when behavior deviates from expected patterns, such as a container spawning a shell, writing to unexpected paths, or making outbound network calls it should not be making.

Here is a custom Falco rule to detect shell execution inside a container:

- rule: Shell Spawned in Container
 desc: Detects shell execution inside a running container
 condition: >
   spawned_process and container and
   proc.name in (shell_binaries) and
   not proc.pname in (known_shell_parents)
 output: >
   Shell spawned in container
   (user=%user.name container=%container.name
   image=%container.image.repository:%container.image.tag
   shell=%proc.name parent=%proc.pname)
 priority: WARNING
 tags: [container, shell, runtime]

[cta]

Kubernetes Security: Locking Down Your Cluster

Kubernetes is powerful and flexible, which also means it has a large configuration surface that can be exploited if left in default settings. Cluster hardening is non-negotiable for any production environment.

Pod Security with SecurityContext

Every pod spec should include an explicit SecurityContext that restricts what the container is allowed to do at the kernel level:

apiVersion: v1
kind: Pod
metadata:
 name: secure-pod
spec:
 securityContext:
   runAsNonRoot: true
   runAsUser: 10001
   fsGroup: 10001
   seccompProfile:
     type: RuntimeDefault
 containers:
   - name: app
     image: myapp:latest
     securityContext:
       allowPrivilegeEscalation: false
       readOnlyRootFilesystem: true
       capabilities:
         drop:
           - ALL
     resources:
       limits:
         cpu: "500m"
         memory: "256Mi"
       requests:
         cpu: "100m"
         memory: "128Mi"

[cta]

Breaking down the key fields: runAsNonRoot enforces that the container cannot run as root, readOnlyRootFilesystem prevents writes to the container filesystem (making persistence harder for attackers), allowPrivilegeEscalation: false prevents the process from gaining additional privileges, and capabilities: drop: ALL removes all Linux capabilities by default.

Network Policies: Zero-Trust Between Pods

By default, all pods in a Kubernetes cluster can communicate with all other pods. This lateral movement capability is exactly what an attacker wants after gaining a foothold. Network Policies restrict pod-to-pod communication to only what is explicitly permitted.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
 name: deny-all-ingress
 namespace: production
spec:
 podSelector: {}
 policyTypes:
   - Ingress
   - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
 name: allow-frontend-to-backend
 namespace: production
spec:
 podSelector:
   matchLabels:
     app: backend
 ingress:
   - from:
       - podSelector:
           matchLabels:
             app: frontend
     ports:
       - protocol: TCP
         port: 8080

[cta]

The first policy denies all ingress and egress by default. The second selectively opens port 8080 from the frontend to the backend only. This is a proper zero-trust posture at the network layer.

Auditing Kubernetes RBAC with kubectl-who-can

Overpermissive RBAC is one of the most common Kubernetes misconfigurations. The kubectl-who-can plugin lets you query which subjects (users, groups, service accounts) can perform specific actions:

# Install the plugin via krew
kubectl krew install who-can

# Check who can create pods in the production namespace
kubectl who-can create pods -n production

# Check who can exec into pods (a critical privilege)
kubectl who-can create pods/exec -n production

# Check who has wildcard resource access
kubectl who-can '*' '*' -n production

[cta]

Any service account or user that can exec into pods, create privileged pods, or access secrets without restriction should be treated as a critical finding and remediated immediately.

If you want structured training on Kubernetes attack and defense techniques, including privilege escalation paths and remediation strategies, Redfox Cybersecurity Academy offers dedicated cloud security labs that mirror production environments.

Infrastructure as Code Security: Catching Misconfigs Before Deployment

Infrastructure as Code (IaC) tools like Terraform, Helm, and CloudFormation let you define cloud resources declaratively. They also let you accidentally expose S3 buckets, open security groups to the world, or create IAM roles with admin privileges, all in version control.

Scanning Terraform with Checkov

Checkov by Bridgecrew scans IaC files for security and compliance misconfigurations before they are ever deployed.

# Install Checkov
pip install checkov

# Scan a Terraform directory
checkov -d ./terraform --framework terraform

# Scan and output results as JUnit XML for CI integration
checkov -d ./terraform --output junitxml > checkov-results.xml

# Run only specific checks (e.g., CKV_AWS_18 for S3 access logging)
checkov -d ./terraform --check CKV_AWS_18,CKV_AWS_19,CKV_AWS_20

[cta]

Common Terraform misconfigurations Checkov catches include S3 buckets without server-side encryption, security groups with 0.0.0.0/0 ingress on port 22, RDS instances with public access enabled, and IAM policies with wildcard actions.

Here is an example of a secure Terraform S3 bucket configuration that Checkov would pass:

resource "aws_s3_bucket" "secure_bucket" {
 bucket = "my-secure-app-bucket"
}

resource "aws_s3_bucket_versioning" "secure_bucket_versioning" {
 bucket = aws_s3_bucket.secure_bucket.id
 versioning_configuration {
   status = "Enabled"
 }
}

resource "aws_s3_bucket_server_side_encryption_configuration" "secure_bucket_sse" {
 bucket = aws_s3_bucket.secure_bucket.id
 rule {
   apply_server_side_encryption_by_default {
     sse_algorithm = "aws:kms"
   }
 }
}

resource "aws_s3_bucket_public_access_block" "secure_bucket_pab" {
 bucket                  = aws_s3_bucket.secure_bucket.id
 block_public_acls       = true
 block_public_policy     = true
 ignore_public_acls      = true
 restrict_public_buckets = true
}

[cta]

Secrets Management: Never Store Secrets in Config Files

Hard-coded secrets and plaintext environment variables are among the leading causes of cloud credential exposure. Production cloud-native applications should integrate with a dedicated secrets management solution.

Using HashiCorp Vault with Kubernetes

HashiCorp Vault provides dynamic secrets, secret leasing, and fine-grained access control. With the Vault Agent Injector, secrets are injected into pods as files at runtime, never stored in Kubernetes Secrets objects (which are only base64-encoded, not encrypted, by default).

# Pod annotation to inject Vault secrets
apiVersion: v1
kind: Pod
metadata:
 name: app-with-vault-secrets
 annotations:
   vault.hashicorp.com/agent-inject: "true"
   vault.hashicorp.com/role: "my-app-role"
   vault.hashicorp.com/agent-inject-secret-config: "secret/data/myapp/config"
   vault.hashicorp.com/agent-inject-template-config: |
     {{- with secret "secret/data/myapp/config" -}}
     export DB_PASSWORD="{{ .Data.data.db_password }}"
     export API_KEY="{{ .Data.data.api_key }}"
     {{- end }}
spec:
 serviceAccountName: my-app-sa
 containers:
   - name: app
     image: myapp:latest

[cta]

This approach means your application reads secrets from a mounted file that Vault injects and rotates automatically. No secrets ever appear in your deployment manifests or environment variable definitions.

Supply Chain Security: Signing and Verifying Container Images

The SolarWinds and Log4Shell incidents brought software supply chain security into sharp focus. In cloud-native environments, image signing ensures that the container you deploy is exactly the container that was built and verified in your pipeline.

Signing Images with Cosign

Cosign, part of the Sigstore project, lets you sign container images with a private key and verify them before deployment:

# Generate a signing key pair
cosign generate-key-pair

# Sign a container image
cosign sign --key cosign.key myregistry.io/myapp:v1.2.3

# Verify an image signature before deployment
cosign verify --key cosign.pub myregistry.io/myapp:v1.2.3

# Sign with keyless signing (uses OIDC identity)
cosign sign myregistry.io/myapp:v1.2.3

[cta]

You can enforce image signature verification in Kubernetes using a policy engine like Kyverno:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
 name: verify-image-signatures
spec:
 validationFailureAction: Enforce
 rules:
   - name: check-image-signature
     match:
       any:
         - resources:
             kinds:
               - Pod
     verifyImages:
       - imageReferences:
           - "myregistry.io/myapp:*"
         attestors:
           - entries:
               - keys:
                   publicKeys: |-
                     -----BEGIN PUBLIC KEY-----
                     <your-cosign-public-key>
                     -----END PUBLIC KEY-----

[cta]

With this policy in place, Kubernetes will refuse to schedule any pod using an unsigned or improperly signed image from your registry.

Observability and Incident Response in Cloud-Native Environments

Security without visibility is blind. Cloud-native security practices must include comprehensive logging, alerting, and the ability to respond quickly when something goes wrong.

Cloud-Native Audit Logging

Enable Kubernetes audit logging by configuring an audit policy in your cluster:

# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
 - level: RequestResponse
   resources:
     - group: ""
       resources: ["secrets", "configmaps"]
 - level: Metadata
   resources:
     - group: ""
       resources: ["pods", "services"]
 - level: None
   users: ["system:kube-proxy"]
   verbs: ["watch"]
   resources:
     - group: ""
       resources: ["endpoints", "services"]

[cta]

This policy captures full request and response bodies for access to secrets and configmaps (your most sensitive resources), metadata-level logging for pod and service operations, and explicitly drops noisy, low-value events from kube-proxy.

Feed these logs into a SIEM or centralized log platform like the ELK stack or Grafana Loki, and build alerts around high-signal events: exec into a pod, access to secrets from an unexpected service account, new ClusterRoleBindings being created outside of a deployment pipeline.

Key Takeaways

Cloud-native security is not a single tool or a single scan. It is a layered discipline that spans every phase of the application lifecycle.

The core practices covered in this post form a solid foundation: shifting security left with static analysis and secrets scanning, hardening container images and enforcing runtime policies with Falco, applying zero-trust network policies and secure pod specs in Kubernetes, scanning IaC with Checkov before anything is deployed, managing secrets with Vault instead of environment variables, signing and verifying container images with Cosign and Kyverno, and capturing audit logs to support detection and incident response.

Each of these layers removes a class of risk. Together, they make your cloud-native applications significantly harder to compromise, and dramatically easier to investigate when incidents do occur.

If you want to build real expertise in cloud-native security, attack techniques against containerized environments, and hands-on remediation skills, Redfox Cybersecurity Academy provides structured, lab-driven training designed for practitioners working in modern cloud environments.

Copy Code