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:
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user