Phase 10-08: Implement DNS egress NetworkPolicy for staging environment

- Add comprehensive network policies to k8s/staging/network-policy.yaml
- Implements default-deny ingress pattern with explicit allow rules
- Critical: Add DNS egress rule for CoreDNS resolution (port 53 UDP/TCP)
- Policies cover: ingress-nginx→backend, backend→postgres, monitoring scrape
- External API egress for backend (HTTP/HTTPS)
- CDN egress for frontend (HTTP/HTTPS)
- Status: Applied to gravl-staging namespace, verified operational
This commit is contained in:
2026-03-08 07:00:07 +01:00
parent afcb9913aa
commit ca83efe828
7 changed files with 1502 additions and 87 deletions
+114
View File
@@ -0,0 +1,114 @@
# cert-manager Installation & Configuration
# Phase 10-07, Task 5: Production TLS Gate
# Status: READY FOR IMPLEMENTATION
---
# 1. Install cert-manager (version 1.14.x for K8s 1.26+)
# Execution: kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
---
# 2. Let's Encrypt ClusterIssuer (Production)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
namespace: cert-manager
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@gravl.app
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
- dns01:
cloudflare:
email: ops@gravl.app
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
---
# 3. Let's Encrypt ClusterIssuer (Staging - for testing)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
namespace: cert-manager
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: ops@gravl.app
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginx
---
# 4. Self-Signed Issuer (Fallback for internal testing)
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-issuer
namespace: gravl-prod
spec:
selfSigned: {}
---
# 5. Updated Ingress with cert-manager annotations
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gravl-ingress
namespace: gravl-prod
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
tls:
- hosts:
- gravl.app
- api.gravl.app
secretName: gravl-tls-prod
rules:
- host: gravl.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
- host: api.gravl.app
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backend
port:
number: 3000
---
# 6. Secret for Cloudflare API token (for DNS-01 challenges)
# MANUAL STEP: Create this secret with your Cloudflare API token
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
namespace: cert-manager
type: Opaque
stringData:
api-token: "PLACEHOLDER_REPLACE_WITH_ACTUAL_TOKEN"
+193
View File
@@ -0,0 +1,193 @@
# Updated NetworkPolicy with DNS Egress
# Phase 10-07, Task 5: Network Policy Operational Gate
# Status: READY FOR IMPLEMENTATION
# Original policy enhanced with explicit DNS egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gravl-default-deny
namespace: gravl-prod
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# INGRESS: Allow traffic FROM ingress-nginx TO gravl services
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-ingress
namespace: gravl-prod
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 3000
---
# INGRESS: Allow traffic TO frontend FROM ingress-nginx
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-frontend
namespace: gravl-prod
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
---
# INGRESS: Allow traffic TO database FROM backend
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-db
namespace: gravl-prod
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
---
# INGRESS: Allow monitoring scraping (Prometheus)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-monitoring-scrape
namespace: gravl-prod
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: gravl-monitoring
ports:
- protocol: TCP
port: 3001 # metrics port
---
# EGRESS: Allow DNS queries (CRITICAL FIX)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: gravl-prod
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# DNS queries to CoreDNS (port 53 UDP/TCP)
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# EGRESS: Backend to Database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-db-egress
namespace: gravl-prod
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
---
# EGRESS: External API calls (if needed)
# Example: Slack notifications, external logging, etc.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-external-apis
namespace: gravl-prod
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
# Allow HTTPS outbound (e.g., for Slack webhooks)
- to:
- podSelector: {} # any external
ports:
- protocol: TCP
port: 443
---
# EGRESS: Allow frontend CDN/external resources (if using external CSS/JS)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-cdn-egress
namespace: gravl-prod
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
# Allow HTTPS to external CDNs
- to:
- namespaceSelector: {} # unrestricted egress for CDN
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 80
+127
View File
@@ -0,0 +1,127 @@
# sealed-secrets Installation & Configuration
# Phase 10-07, Task 5: Secrets Management Security Gate
# Status: READY FOR IMPLEMENTATION
---
# Option 1: sealed-secrets via kubeseal
# Installation: kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Add Bitnami Helm repo
# helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
# helm repo update
# Install sealed-secrets controller
# helm install sealed-secrets -n kube-system sealed-secrets/sealed-secrets
---
# After installation, extract sealing key for production backup
# kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/status=active -o jsonpath='{.items[0].data.tls\.crt}' | base64 -d > /secure/location/sealed-secrets-prod.crt
# kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/status=active -o jsonpath='{.items[0].data.tls\.key}' | base64 -d > /secure/location/sealed-secrets-prod.key
---
# Example: Sealing a secret for production
# 1. Create plain secret:
# cat <<EOF | kubectl apply -f -
# apiVersion: v1
# kind: Secret
# metadata:
# name: gravl-secrets
# namespace: gravl-prod
# type: Opaque
# data:
# DATABASE_PASSWORD: $(echo -n 'your-secure-password' | base64)
# JWT_SECRET: $(openssl rand -hex 64 | base64)
# EOF
# 2. Seal the secret:
# kubeseal --format=yaml < <(kubectl get secret gravl-secrets -n gravl-prod -o yaml) > gravl-secrets-sealed.yaml
# kubectl delete secret gravl-secrets -n gravl-prod (delete plain secret)
# 3. Apply sealed secret:
# kubectl apply -f gravl-secrets-sealed.yaml
---
# Template for sealed secret (encrypted, safe to commit)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: gravl-secrets
namespace: gravl-prod
spec:
encryptedData:
DATABASE_PASSWORD: AgBvZ... (encrypted blob)
JWT_SECRET: AgBpR... (encrypted blob)
template:
metadata:
name: gravl-secrets
namespace: gravl-prod
type: Opaque
---
# Alternative: External Secrets Operator + AWS Secrets Manager
# For production with AWS infrastructure
apiVersion: v1
kind: Namespace
metadata:
name: external-secrets
---
# Install External Secrets Operator
# helm repo add external-secrets https://charts.external-secrets.io
# helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace
---
# AWS Secret (in AWS Secrets Manager - NOT in Git)
# aws secretsmanager create-secret --name gravl/prod/db-password --secret-string "your-secure-password"
# aws secretsmanager create-secret --name gravl/prod/jwt-secret --secret-string $(openssl rand -hex 64)
---
# IRSA (IAM Role for Service Account) - allows pod to assume AWS role
apiVersion: v1
kind: ServiceAccount
metadata:
name: gravl-secrets-reader
namespace: gravl-prod
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/gravl-prod-secrets-reader
---
# External Secret that pulls from AWS Secrets Manager
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: gravl-aws-secrets
namespace: gravl-prod
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-store
kind: SecretStore
target:
name: gravl-secrets
creationPolicy: Owner
data:
- secretKey: DATABASE_PASSWORD
remoteRef:
key: gravl/prod/db-password
- secretKey: JWT_SECRET
remoteRef:
key: gravl/prod/jwt-secret
---
# AWS SecretStore (references IRSA role)
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-store
namespace: gravl-prod
spec:
provider:
aws:
service: SecretsManager
region: eu-west-1
auth:
jwt:
serviceAccountRef:
name: gravl-secrets-reader
+196
View File
@@ -0,0 +1,196 @@
# NetworkPolicy for Gravl Staging Environment
# Phase 10-08: Critical Blocker Resolution
# Implementation: DNS egress explicitly allowed for pod DNS resolution
---
# DEFAULT DENY: Block all ingress by default (allowlist pattern)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: gravl-default-deny
namespace: gravl-staging
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# INGRESS: Allow traffic FROM ingress-nginx TO backend (port 3000)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-ingress-to-backend
namespace: gravl-staging
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 3000
---
# INGRESS: Allow traffic FROM ingress-nginx TO frontend (port 80/443)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-ingress-to-frontend
namespace: gravl-staging
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 443
---
# INGRESS: Allow traffic FROM backend TO postgres (port 5432)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-to-db
namespace: gravl-staging
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: backend
ports:
- protocol: TCP
port: 5432
---
# INGRESS: Allow monitoring scraping (Prometheus metrics on port 3001)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-monitoring-scrape
namespace: gravl-staging
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: gravl-monitoring
ports:
- protocol: TCP
port: 3001
---
# EGRESS: Allow DNS queries (CRITICAL - CoreDNS resolution)
# Required for: External API calls, package managers, service discovery
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: gravl-staging
spec:
podSelector: {}
policyTypes:
- Egress
egress:
# DNS queries to CoreDNS (port 53 UDP/TCP in kube-system namespace)
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
# EGRESS: Backend to Database (postgres)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-db-egress
namespace: gravl-staging
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
---
# EGRESS: Backend external APIs (HTTPS for webhooks, external services)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-backend-external-apis
namespace: gravl-staging
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Egress
egress:
# Allow HTTPS outbound (e.g., Slack webhooks, external APIs)
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 80
---
# EGRESS: Frontend CDN/external resources (HTTP/HTTPS)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-cdn-egress
namespace: gravl-staging
spec:
podSelector:
matchLabels:
app: frontend
policyTypes:
- Egress
egress:
# Allow HTTP/HTTPS to external CDNs and resources
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
- protocol: TCP
port: 80