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.
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" 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.
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.
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]
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.
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.
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]
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 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.
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.
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.
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 (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.
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]
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.
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.
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.
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.
Security without visibility is blind. Cloud-native security practices must include comprehensive logging, alerting, and the ability to respond quickly when something goes wrong.
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.
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.