Introduction to NGINX Service Mesh Security Hardening
NGINX Service Mesh is a lightweight, high-performance service mesh that provides advanced traffic management, security, and observability for microservices running in Kubernetes. It leverages NGINX Plus as a sidecar proxy to handle east-west traffic between services. Security hardening in this context means configuring the mesh to enforce strict identity verification, encrypt all inter-service communication, and limit access to only what is explicitly permitted. This tutorial walks you through the essential security hardening configurations and best practices to protect your service mesh deployment from common threats and misconfigurations.
Why Security Hardening Matters in a Service Mesh
A service mesh introduces a new layer to your infrastructure. While it abstracts away networking complexity, it also expands the attack surface if not properly secured. Without hardening, you risk:
- Unauthorized service-to-service calls that bypass application-level security
- Man-in-the-middle attacks on unencrypted internal traffic
- Compromised sidecars becoming pivots for lateral movement
- Exposed ingress gateways leaking sensitive data to external attackers
- Misconfigured RBAC policies granting overly broad access
Security hardening addresses these risks by implementing defense-in-depth at the network, transport, and application layers. In NGINX Service Mesh, this translates to mandatory mTLS, fine-grained access control, secure ingress configuration, and continuous policy validation.
Core Security Pillars of NGINX Service Mesh
NGINX Service Mesh security rests on four interconnected pillars. Understanding each is critical before you begin hardening your deployment.
1. Mutual TLS (mTLS) Enforcement
mTLS ensures that every service in the mesh authenticates itself to every other service using X.509 certificates. Both the client and server present certificates, cryptographically proving their identities before any application data is exchanged. NGINX Service Mesh uses SPIRE (the SPIFFE Runtime Environment) as its identity provider, issuing SPIFFE-compliant identities to each workload.
2. Role-Based Access Control (RBAC)
NGINX Service Mesh allows you to define fine-grained access policies that specify which services may communicate with one another, which HTTP methods and paths are allowed, and from which namespaces requests may originate. These policies are enforced at the sidecar proxy level, meaning a request is denied before it ever reaches the application.
3. Secure Ingress Gateway Configuration
The ingress gateway is the entry point for external traffic into the mesh. Hardening the ingress gateway involves TLS termination with strong cipher suites, strict SNI validation, rate limiting, and Web Application Firewall (WAF) integration to filter malicious payloads before they enter the mesh.
4. Observability and Audit Logging
Comprehensive logging and metrics provide visibility into all security events. NGINX Service Mesh exports access logs, security policy violation metrics, and certificate expiry data that enable SOC teams to detect anomalies and respond to incidents quickly.
Step-by-Step: Hardening a Fresh NGINX Service Mesh Deployment
Let's walk through a practical hardening process. We assume you have a Kubernetes cluster and the NGINX Service Mesh CLI installed. The steps progress from foundational security to advanced policy enforcement.
Step 1: Deploy the Mesh with Strict mTLS Mode
By default, NGINX Service Mesh operates in permissive mTLS mode, which allows both encrypted and unencrypted traffic during migration. For a hardened production environment, you must enable strict mode from the start.
# Deploy NGINX Service Mesh with strict mTLS
nginx-meshctl deploy \
--mtls-mode strict \
--mtls-ca-ttl 168h \
--mtls-svid-ttl 1h \
--image-version latest
Key parameters explained:
--mtls-mode strict: Rejects any unencrypted or unauthenticated connection between mesh services--mtls-ca-ttl 168h: Sets the CA certificate lifetime to 7 days, forcing regular rotation--mtls-svid-ttl 1h: Short-lived service identity certificates (1 hour) minimize the window of exposure if a certificate is compromised
Step 2: Verify Certificate Issuance and Rotation
After deployment, confirm that SPIRE is issuing certificates correctly and that automatic rotation functions as expected.
# Check SPIRE agent status across nodes
kubectl get pods -n nginx-mesh -l app=spire-agent -o wide
# Inspect the SVID of a specific service
kubectl exec -it deployment/my-service -c nginx-mesh-sidecar -- \
openssl s_client -connect localhost:443 -showcerts 2>&1 | \
grep -A 20 "BEGIN CERTIFICATE"
# Force certificate rotation for testing
kubectl exec -it deployment/my-service -c nginx-mesh-sidecar -- \
curl -X POST http://localhost:8080/spire-agent/api/v1/svid/rotate
Automated rotation happens transparently. The mesh sidecar fetches a new SVID before the current one expires, ensuring zero-downtime credential refresh.
Step 3: Create a Namespace-Level Traffic Policy
Begin RBAC hardening by isolating namespaces. This policy prevents services in the default namespace from calling services in payments unless explicitly allowed.
# traffic-policy-isolation.yaml
apiVersion: nginx.com/v1alpha1
kind: TrafficPolicy
metadata:
name: namespace-isolation
namespace: payments
spec:
accessControl:
allow:
- source:
namespace: "payments"
- source:
namespace: "gateway"
deny:
- source:
namespace: "*"
defaultAction: deny
Apply the policy:
kubectl apply -f traffic-policy-isolation.yaml
Now any service from default or other namespaces attempting to reach payments services will receive a 403 Forbidden response at the proxy level.
Step 4: Define Service-Specific Access Control Lists
Namespace isolation is coarse-grained. Next, create per-service ACLs that restrict HTTP methods and paths. The following example protects a payment processing endpoint.
# payment-acl.yaml
apiVersion: nginx.com/v1alpha1
kind: AccessControlPolicy
metadata:
name: payment-service-acl
namespace: payments
spec:
rules:
- match:
methods: ["POST"]
paths: ["/api/v1/process"]
allow:
- sourceService: "order-service.payments.svc.cluster.local"
- sourceService: "gateway-service.gateway.svc.cluster.local"
deny:
- sourceService: "*"
- match:
methods: ["GET"]
paths: ["/health", "/metrics"]
allow:
- sourceService: "*"
Apply and verify:
kubectl apply -f payment-acl.yaml
# Test allowed access from order-service
kubectl exec -it deployment/order-service -c nginx-mesh-sidecar -- \
curl -X POST https://payment-service.payments.svc.cluster.local/api/v1/process \
-H "Content-Type: application/json" -d '{"amount": 100}'
# Test denied access from an unauthorized service
kubectl exec -it deployment/analytics-service -c nginx-mesh-sidecar -- \
curl -X POST https://payment-service.payments.svc.cluster.local/api/v1/process \
-H "Content-Type: application/json" -d '{"amount": 100}'
# Expected: 403 Forbidden
Hardening the Ingress Gateway
The ingress gateway is the most exposed component. A hardened configuration follows these principles: terminate TLS with modern ciphers, enforce SNI host matching, apply rate limiting, and integrate a WAF.
Configuring TLS Termination with Strong Cipher Suites
# ingress-gateway-tls.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ingress-tls-config
namespace: nginx-mesh
data:
ssl-ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"
ssl-protocols: "TLSv1.2 TLSv1.3"
ssl-prefer-server-ciphers: "on"
ssl-session-tickets: "off"
ssl-stapling: "on"
ssl-stapling-verify: "on"
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: secure-ingress
annotations:
nginx.org/ssl-services: "backend-service"
nginx.org/ssl-ciphers: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384"
nginx.org/ssl-prefer-server-ciphers: "true"
nginx.org/proxy-ssl-protocols: "TLSv1.2 TLSv1.3"
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls-secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gateway-service
port:
number: 443
This configuration disables legacy TLS versions, removes weak ciphers, enables OCSP stapling for faster certificate revocation checks, and disables session tickets to prevent session resumption attacks.
Adding Rate Limiting to Prevent DDoS
# rate-limit-policy.yaml
apiVersion: nginx.com/v1alpha1
kind: RateLimitPolicy
metadata:
name: ingress-rate-limit
namespace: gateway
spec:
rules:
- match:
host: "api.example.com"
paths: ["/api/v1/*"]
rateLimit:
rate: 100
burst: 20
key: "${remote_addr}"
delay: 5
- match:
paths: ["/health"]
rateLimit:
rate: 1000
burst: 50
key: "${remote_addr}"
Apply the rate limit policy to the ingress gateway deployment:
kubectl apply -f rate-limit-policy.yaml
# Verify the rate limit is active
for i in $(seq 1 150); do
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/api/v1/test
echo ""
done
# After burst limit, you will see 503 Service Unavailable responses
WAF Integration with NGINX App Protect
For production hardening, integrate NGINX App Protect (NAAP) as a WAF module in the ingress gateway. This requires an NGINX Plus subscription with the App Protect module enabled.
# app-protect-policy.yaml
apiVersion: k8s.nginx.org/v1alpha1
kind: APPolicy
metadata:
name: waf-policy
spec:
policy:
signature-settings:
signature-inclusion-policy: "strict"
threat-campaigns:
- name: "command-execution"
action: "block"
- name: "sql-injection"
action: "block"
- name: "cross-site-scripting"
action: "block"
enforcement-mode: "blocking"
blocking-settings:
violations:
- name: "VIOL_ASM_PARAMETER_TYPE_MISMATCH"
alarm: true
block: true
---
apiVersion: k8s.nginx.org/v1alpha1
kind: APLogConf
metadata:
name: waf-logging
spec:
filter:
request_type: "all"
minimum_severity: "critical"
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: waf-protected-ingress
annotations:
nginx.org/app-protect-policy: "waf-policy"
nginx.org/app-protect-logconf: "waf-logging"
nginx.org/app-protect-enable: "true"
spec:
tls:
- hosts:
- api.example.com
secretName: api-tls-secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gateway-service
port:
number: 443
The WAF blocks SQL injection, cross-site scripting, command execution, and parameter type mismatch attacks at the ingress boundary before they ever reach backend services.
Sidecar Security Hardening
The NGINX sidecar proxy itself must be hardened to prevent container escapes and privilege escalation. Apply Kubernetes Pod Security Standards and network policies to restrict the sidecar's capabilities.
Applying Pod Security Context
# secure-sidecar-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
namespace: payments
spec:
replicas: 3
selector:
matchLabels:
app: payment-service
template:
metadata:
labels:
app: payment-service
spec:
serviceAccountName: payment-service-sa
securityContext:
runAsNonRoot: true
runAsUser: 101
fsGroup: 101
containers:
- name: app
image: my-app:latest
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
- name: nginx-mesh-sidecar
image: nginx-mesh-sidecar:latest
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
This configuration ensures the sidecar runs as a non-root user, has a read-only root filesystem, drops all Linux capabilities except the minimum required to bind to network ports, and prevents privilege escalation.
Network Policies to Isolate Sidecar Communication
# mesh-network-policy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: sidecar-isolation
namespace: payments
spec:
podSelector:
matchLabels:
app: payment-service
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
mesh-enabled: "true"
- podSelector:
matchLabels:
app: order-service
ports:
- protocol: TCP
port: 443
- from:
- namespaceSelector:
matchLabels:
name: nginx-mesh
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
mesh-enabled: "true"
ports:
- protocol: TCP
port: 443
- to:
- namespaceSelector:
matchLabels:
name: nginx-mesh
ports:
- protocol: TCP
port: 8443
Apply the network policy:
kubectl apply -f mesh-network-policy.yaml
# Verify the policy is enforced
kubectl describe networkpolicy sidecar-isolation -n payments
This restricts the sidecar to communicate only with other mesh-enabled pods on port 443 (mTLS data plane) and the NGINX Service Mesh control plane on designated ports. Any attempt to reach external endpoints from the sidecar is blocked.
Certificate and SPIFFE Identity Best Practices
The SPIFFE identity system underpins all authentication in NGINX Service Mesh. Hardening this subsystem requires careful attention to trust domain configuration, certificate lifetimes, and federation boundaries.
Configuring Trust Domain and Identity Templates
# spire-server-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: spire-server-config
namespace: nginx-mesh
data:
server.conf: |
server:
bind_address: 0.0.0.0
bind_port: 8081
trust_domain: "production.example.com"
ca_ttl: 168h
ca_subject:
country: "US"
organization: "Example Corp"
common_name: "production.example.com"
plugins:
DataStore:
sql:
plugin_data:
database_type: "sqlite3"
connection_string: "/var/lib/spire/data/datastore.sqlite3"
NodeAttestor:
k8s_psat:
plugin_data:
cluster: "prod-cluster"
KeyManager:
memory:
plugin_data: {}
UpstreamAuthority:
disk:
plugin_data:
key_file_path: "/etc/spire/keys/privkey.pem"
cert_file_path: "/etc/spire/keys/cacert.pem"
Set the trust domain to a unique, organization-specific value like production.example.com. This prevents cross-mesh identity spoofing if multiple meshes exist in your infrastructure.
Rotating the SPIRE CA Regularly
# Manual CA rotation script
#!/bin/bash
# rotate-spire-ca.sh
# Generate new CA key and certificate
openssl req -new -x509 -days 365 -nodes \
-subj "/C=US/O=Example Corp/CN=production.example.com" \
-keyout new-ca-key.pem \
-out new-ca-cert.pem \
-addext "subjectAltName=DNS:production.example.com"
# Create a ConfigMap with the new CA certificate
kubectl create configmap spire-ca-cert \
--from-file=cacert.pem=new-ca-cert.pem \
--namespace nginx-mesh \
--dry-run=client -o yaml | kubectl apply -f -
# Restart SPIRE server to pick up the new CA
kubectl rollout restart deployment/spire-server -n nginx-mesh
# Verify all agents have reconnected
kubectl wait --for=condition=ready pod \
-l app=spire-agent -n nginx-mesh \
--timeout=300s
Automate CA rotation with a cron job that runs every 30 days. Always keep the previous CA valid for a transition period (e.g., 24 hours) to allow all SVIDs to rotate gracefully.
Audit Logging and Security Monitoring
Even with all hardening measures in place, you need continuous visibility. NGINX Service Mesh provides detailed access logs and Prometheus metrics that feed into your SIEM and monitoring stack.
Enabling Comprehensive Access Logging
# access-log-config.yaml
apiVersion: nginx.com/v1alpha1
kind: AccessLogPolicy
metadata:
name: full-access-logging
namespace: payments
spec:
filters:
- paths: ["/api/*"]
logFormat: |
"$time_iso8601" "$remote_addr" "$ssl_client_fingerprint"
"$server_name" "$request_method" "$request_uri"
"$status" "$body_bytes_sent" "$request_time"
"$upstream_addr" "$upstream_status"
"$spiffe_id" "$request_id"
destinations:
- syslog:
host: "siem-collector.monitoring.svc.cluster.local"
port: 514
protocol: "tcp"
facility: "local0"
severity: "info"
- stdout:
format: "json"
Apply the logging policy:
kubectl apply -f access-log-config.yaml
# Verify logs are flowing to your SIEM collector
kubectl logs deployment/siem-collector -n monitoring | grep "spiffe_id"
Prometheus Metrics for Security Events
NGINX Service Mesh exports metrics that can be scraped by Prometheus. Key security metrics include:
# Prometheus scrape configuration snippet
scrape_configs:
- job_name: 'nginx-mesh-sidecars'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_container_name]
action: keep
regex: nginx-mesh-sidecar
metric_relabel_configs:
- source_labels: [__name__]
action: keep
regex: 'nginx_mesh_(auth|ssl|request)_(.*)'
Key metrics to alert on:
nginx_mesh_auth_failures_total: Spike indicates potential attack or misconfigurationnginx_mesh_ssl_handshake_failures_total: Certificate issues or downgrade attacksnginx_mesh_request_blocked_by_policy_total: Policy violations worth investigatingnginx_mesh_svid_expiry_seconds: Alert when less than 300 seconds remain before expiry
Sample Prometheus Alert Rules
# prometheus-alerts.yaml
groups:
- name: nginx-mesh-security
rules:
- alert: HighAuthFailures
expr: rate(nginx_mesh_auth_failures_total[5m]) > 10
for: 2m
labels:
severity: critical
annotations:
summary: "High rate of mTLS authentication failures"
description: "Service {{ $labels.pod }} in namespace {{ $labels.namespace }} has elevated auth failures. Investigate possible certificate issue or unauthorized access attempt."
- alert: CertificateExpiryCritical
expr: nginx_mesh_svid_expiry_seconds < 300
for: 1m
labels:
severity: critical
annotations:
summary: "SVID certificate expiring in less than 5 minutes"
description: "Pod {{ $labels.pod }} SVID expires soon. Check SPIRE agent connectivity."
- alert: PolicyBlockRateIncrease
expr: rate(nginx_mesh_request_blocked_by_policy_total[10m]) > 20
for: 5m
labels:
severity: warning
annotations:
summary: "Increased rate of policy-blocked requests"
description: "Possible misconfiguration or active reconnaissance against namespace {{ $labels.namespace }}."
Best Practices Summary
The following checklist consolidates the most critical security hardening practices for NGINX Service Mesh. Use it as a reference during deployment and periodic audits.
Deployment-Time Hardening
- Always deploy with
--mtls-mode strict— never use permissive mode in production - Set SVID TTL to 1 hour or less to limit credential exposure window
- Configure a unique trust domain that reflects your organizational identity
- Deploy the SPIRE server with persistent storage to survive restarts without losing registration data
- Use Kubernetes secrets for all SPIFFE CA private keys — never store them in ConfigMaps
Runtime Hardening
- Apply Pod Security Standards at the
restrictedlevel for all mesh-injected pods - Use Kubernetes NetworkPolicies to restrict sidecar egress to only the mesh control plane and other mesh services
- Implement namespace-scoped TrafficPolicies with
defaultAction: deny - Define per-service AccessControlPolicies that whitelist specific HTTP methods and paths
- Enable OCSP stapling and disable TLS session tickets on ingress gateways
- Integrate a WAF (NGINX App Protect or ModSecurity) on all ingress gateways
Operational Hardening
- Automate SPIRE CA rotation on a 30-day schedule with a 24-hour overlap period
- Ship all access logs to a centralized SIEM with SPIFFE ID correlation
- Alert on auth failures, certificate expiry, and policy block rate anomalies
- Conduct monthly policy reviews to remove stale or overly permissive rules
- Run periodic penetration tests against the ingress gateway and internal mesh boundaries
- Keep the NGINX Service Mesh control plane and sidecar images updated to the latest stable versions
Secrets Management
- Store ingress TLS certificates in Kubernetes Secrets with
kubernetes.io/tlstype - Rotate ingress certificates at least every 90 days, preferably via cert-manager automation
- Never expose SPIFFE CA material in version control or CI/CD pipelines
- Use sealed secrets or a secrets management service (HashiCorp Vault, AWS Secrets Manager) for any mesh-sensitive configuration
Disaster Recovery and Incident Response
Despite best efforts, security incidents can occur. Prepare response procedures specifically for mesh-level compromises.
Isolating a Compromised Service
# Immediately apply a deny-all policy for the compromised service
cat <
Revoking and Reissuing Mesh-Wide Certificates
# Emergency certificate revocation procedure
# 1. Rotate the SPIRE CA immediately
kubectl delete configmap spire-ca-cert -n nginx-mesh
kubectl create configmap spire-ca-cert \
--from-file=cacert.pem=emergency-ca-cert.pem \
--namespace nginx-mesh
# 2. Restart SPIRE server and all agents
kubectl rollout restart deployment/spire-server -n nginx-mesh
kubectl delete pods -l app=spire-agent -n nginx-mesh
# 3. Force all sidecars to fetch new SVIDs
for deployment in $(kubectl get deployments -A -o json | \
jq -r '.items[] | select(.spec.template.metadata.labels["nginx-mesh-sidecar"] == "true") | "\(.metadata.namespace)/\(.metadata.name)"'); do
kubectl rollout restart deployment -n "${deployment%/*}" "${deployment#*/}"
done
# 4. Verify all pods are healthy with new certificates
kubectl wait --for=condition=ready pod -A \
-l nginx-mesh-sidecar=true --timeout=600s
Validation and Testing Your Hardened Mesh
After applying hardening configurations, validate that security controls work as expected. Automated testing catches regressions during future changes.
Automated Security Test Suite
# security-validation.sh
#!/bin/bash
echo "=== NGINX Service Mesh Security Validation ==="
# Test 1: Verify strict mTLS - unauthenticated request should fail
echo "[Test 1] Testing mTLS enforcement..."
AUTH_FAIL=$(kubectl exec deployment/test-runner -- \
curl -k -s -o /dev/null -w "%{http_code}" \
https://payment-service.payments.svc.cluster.local/health)
if [ "$AUTH_FAIL" -eq "403" ] || [ "$AUTH_FAIL" -eq "000" ]; then
echo "PASS: Unauthenticated requests blocked ($AUTH_FAIL)"
else
echo "FAIL: mTLS not enforced, got HTTP $AUTH_FAIL"
exit 1
fi
# Test 2: Verify namespace isolation
echo "[Test 2] Testing namespace isolation..."
NS_ISO=$(kubectl exec deployment/default-service -n default -- \
curl -k -s -o /dev/null -w "%{http_code}" \
https://payment-service.payments.svc.cluster.local/api/v1/status)
if [ "$NS_ISO" -eq "403" ]; then
echo "PASS: Cross-namespace access denied"
else
echo "FAIL: Namespace isolation not enforced, got HTTP $NS_ISO"
exit 1
fi
# Test 3: Verify ACL path restrictions
echo "[Test 3] Testing ACL path restrictions..."
ACL_TEST=$(kubectl exec deployment/order-service -n payments -- \
curl -k -s -o /dev/null -w "%{http_code}" \
https://payment-service.payments.svc.cluster.local/api/v1/admin)
if [ "$ACL_TEST" -eq "403" ]; then
echo "PASS: Unauthorized path blocked"
else
echo "FAIL: ACL not enforced, got HTTP $ACL_TEST"
exit 1
fi
# Test 4: Verify ingress WAF blocking
echo "[Test 4] Testing WAF SQL injection blocking..."
WAF_TEST=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Host: api.example.com" \
"https://ingress-gateway/api/v1/search?q=1'+OR+'1'='1")
if [ "$WAF_TEST" -eq "403" ] || [ "$WAF_TEST" -eq "406" ]; then
echo "PASS: SQL injection blocked by WAF"
else
echo "FAIL: WAF did not block SQL injection, got HTTP $WAF_TEST"
exit 1
fi
# Test 5: Verify rate limiting
echo "[Test 5] Testing rate limiting..."
RATE_LIMIT_COUNT=0
for i in $(seq 1 150); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Host: api.example.com" \
"https://ingress-gateway/api/v1/test" 2>/dev/null)
if [ "$STATUS" -eq "503" ]; then
RATE_LIMIT_COUNT=$((RATE_LIMIT_COUNT + 1))
fi
done
if [ "$RATE_LIMIT_COUNT" -gt 0 ]; then
echo "PASS: Rate limiting active ($RATE_LIMIT_COUNT requests blocked)"
else
echo "FAIL: Rate limiting not enforced"
exit 1
fi
echo ""
echo "=== All security tests passed ==="
Run this test suite as part of your CI/CD pipeline after any mesh configuration change. A failing test blocks deployment to production.
Continuous Compliance with OPA Gatekeeper
# gatekeeper-constraint.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-mesh-security-labels
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
labels:
- key: "nginx-mesh-security-policy"
allowedRegex: "^((strict)|(permissive-test-only))$"
---
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | input.parameters.labels[label].key}
missing := required - provided
count(missing) > 0
msg := sprintf("Missing required labels: %v", [missing])
}
This Gatekeeper policy ensures every deployment in the mesh carries a security policy label, preventing accidental deployment of services without mesh security configurations.
Conclusion
NGINX Service Mesh security hardening is not a one-time effort but an ongoing discipline. By enforcing strict mTLS, implementing fine-grained RBAC policies, hardening ingress gateways with WAF protection, restricting sidecar privileges, and establishing continuous monitoring and automated testing, you create a defense-in-depth architecture that protects microservices at every layer. The practices outlined in this tutorial—from deployment-time strict mode to emergency isolation procedures—form a comprehensive security framework. Regularly review your policies, rotate certificates on schedule, and keep the mesh control plane updated. A hardened service mesh becomes a powerful security asset rather than an additional attack surface, giving your organization confidence that inter-service communication remains authenticated, authorized, encrypted, and continuously audited.