diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 4472b870..33916096 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -112,3 +112,22 @@ jobs: tags: ${{ steps.tags.outputs.tags }} cache-from: type=registry,ref=${{ matrix.image }}:buildcache cache-to: type=registry,ref=${{ matrix.image }}:buildcache,mode=max + + # EN: Scan image for vulnerabilities with Trivy (DEVOPS-M-01) + # VI: Quet lo hong bao mat image bang Trivy (DEVOPS-M-01) + - name: Scan ${{ matrix.service }} image for vulnerabilities + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ matrix.image }}:${{ github.sha }} + format: 'sarif' + output: 'trivy-results-${{ matrix.service }}.sarif' + severity: 'CRITICAL,HIGH' + exit-code: '1' + ignore-unfixed: true + + - name: Upload Trivy scan results to GitHub Security tab + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results-${{ matrix.service }}.sarif' + category: 'trivy-${{ matrix.service }}' diff --git a/deployments/local/docker-compose.yml b/deployments/local/docker-compose.yml index 11e6e120..4366cbb1 100644 --- a/deployments/local/docker-compose.yml +++ b/deployments/local/docker-compose.yml @@ -68,6 +68,22 @@ services: timeout: 5s retries: 5 + # Redis Exporter - Prometheus metrics for Redis (DEVOPS-W-01) + redis-exporter: + image: oliver006/redis_exporter:latest + container_name: redis-exporter-local + environment: + REDIS_ADDR: "redis://redis:6379" + REDIS_PASSWORD: "goodgo-redis-local" + ports: + - "9121:9121" + networks: + - microservices-network + depends_on: + redis: + condition: service_healthy + restart: unless-stopped + # MinIO - Object Storage (S3-compatible) minio: image: minio/minio:latest diff --git a/deployments/staging/kubernetes/network-policy.yaml b/deployments/staging/kubernetes/network-policy.yaml new file mode 100644 index 00000000..8acc0c11 --- /dev/null +++ b/deployments/staging/kubernetes/network-policy.yaml @@ -0,0 +1,370 @@ +# EN: Kubernetes NetworkPolicy — default-deny + explicit allow rules (DEVOPS-W-05) +# VI: Kubernetes NetworkPolicy — tu choi mac dinh + cho phep tuong minh (DEVOPS-W-05) +# +# Strategy: +# 1. Default-deny all ingress and egress in staging namespace +# 2. Allow DNS resolution (kube-dns) +# 3. Allow inter-service communication via explicit rules +# 4. Allow ingress from Traefik (API gateway) +# 5. Allow Prometheus scraping metrics from all services +# 6. Allow Redis access only from application services + +# ============================================================================= +# Default Deny — block all ingress + egress by default +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-all + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + +--- +# ============================================================================= +# Allow DNS (kube-dns) — required for all pods to resolve service names +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-dns-egress + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: {} + policyTypes: + - Egress + egress: + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + +--- +# ============================================================================= +# Allow Traefik ingress → microservices (port 8080) +# EN: Traefik runs in kube-system namespace (or traefik namespace). +# VI: Traefik chay trong namespace kube-system (hoac traefik namespace). +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-traefik-ingress + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - iam-service + - merchant-service + - order-service + - fnb-engine + - inventory-service + - wallet-service + - catalog-service + - storage-service + - booking-service + - pos-web + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik + ports: + - port: 8080 + protocol: TCP + +--- +# ============================================================================= +# Allow microservices to egress to each other (port 8080) +# EN: Services communicate internally via REST (HTTP/8080). +# VI: Cac service giao tiep noi bo qua REST (HTTP/8080). +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-inter-service-egress + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - iam-service + - merchant-service + - order-service + - fnb-engine + - inventory-service + - wallet-service + - catalog-service + - storage-service + - booking-service + policyTypes: + - Egress + egress: + - to: + - podSelector: + matchExpressions: + - key: app + operator: In + values: + - iam-service + - merchant-service + - order-service + - fnb-engine + - inventory-service + - wallet-service + - catalog-service + - storage-service + - booking-service + ports: + - port: 8080 + protocol: TCP + +--- +# ============================================================================= +# Allow Redis access — only from app services (port 6379) +# EN: Redis is accessed only by application pods (not pos-web frontend). +# VI: Redis chi duoc truy cap boi cac pod ung dung (khong phai pos-web frontend). +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-redis-ingress + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - redis-master + - redis-replica + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchExpressions: + - key: app + operator: In + values: + - iam-service + - merchant-service + - order-service + - fnb-engine + - inventory-service + - wallet-service + - catalog-service + - storage-service + - booking-service + ports: + - port: 6379 + protocol: TCP + +--- +# Allow Redis master → replica replication egress +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-redis-replication + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - redis-master + - redis-replica + - redis-sentinel + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: + matchExpressions: + - key: app + operator: In + values: + - redis-master + - redis-replica + - redis-sentinel + ports: + - port: 6379 + protocol: TCP + - port: 26379 + protocol: TCP + egress: + - to: + - podSelector: + matchExpressions: + - key: app + operator: In + values: + - redis-master + - redis-replica + - redis-sentinel + ports: + - port: 6379 + protocol: TCP + - port: 26379 + protocol: TCP + +--- +# ============================================================================= +# Allow microservices to reach Redis (egress) +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-app-to-redis-egress + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - iam-service + - merchant-service + - order-service + - fnb-engine + - inventory-service + - wallet-service + - catalog-service + - storage-service + - booking-service + policyTypes: + - Egress + egress: + - to: + - podSelector: + matchExpressions: + - key: app + operator: In + values: + - redis-master + - redis-replica + - redis-sentinel + ports: + - port: 6379 + protocol: TCP + - port: 26379 + protocol: TCP + +--- +# ============================================================================= +# Allow Prometheus scrape — port 8080 metrics from all app pods +# EN: Prometheus runs in monitoring namespace and scrapes /metrics. +# VI: Prometheus chay trong namespace monitoring va thu thap /metrics. +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-prometheus-scrape + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - iam-service + - merchant-service + - order-service + - fnb-engine + - inventory-service + - wallet-service + - catalog-service + - storage-service + - booking-service + - pos-web + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - port: 8080 + protocol: TCP + +--- +# ============================================================================= +# Allow microservices to reach external services (Neon PostgreSQL, etc.) +# EN: Egress to external HTTPS/PostgreSQL (Neon cloud DB, SMTP, etc.) +# VI: Egress ra ngoai HTTPS/PostgreSQL (Neon cloud DB, SMTP, v.v.) +# ============================================================================= +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-external-egress + namespace: staging + labels: + environment: staging + platform: goodgo +spec: + podSelector: + matchExpressions: + - key: app + operator: In + values: + - iam-service + - merchant-service + - order-service + - fnb-engine + - inventory-service + - wallet-service + - catalog-service + - storage-service + - booking-service + policyTypes: + - Egress + egress: + - ports: + - port: 443 + protocol: TCP + - port: 5432 + protocol: TCP + - port: 5671 + protocol: TCP + - port: 5672 + protocol: TCP diff --git a/deployments/staging/kubernetes/redis-sentinel.yaml b/deployments/staging/kubernetes/redis-sentinel.yaml new file mode 100644 index 00000000..6b8b437d --- /dev/null +++ b/deployments/staging/kubernetes/redis-sentinel.yaml @@ -0,0 +1,427 @@ +# EN: Redis HA with Sentinel — replaces single-instance redis.yaml (DEVOPS-W-04) +# VI: Redis HA voi Sentinel — thay the redis.yaml don le (DEVOPS-W-04) +# +# Architecture: +# - redis-master: 1 pod StatefulSet (read/write) +# - redis-replica: 2 pod StatefulSet (read-only replicas) +# - redis-sentinel: 3 pod StatefulSet (monitors master, auto-failover) +# +# EN: Apply this file AND remove (or stop applying) redis.yaml to avoid conflicts. +# VI: Apply file nay VA xoa (hoac ngung apply) redis.yaml de tranh conflict. + +# ============================================================================= +# ConfigMap — Sentinel configuration template +# ============================================================================= +apiVersion: v1 +kind: ConfigMap +metadata: + name: redis-sentinel-config + namespace: staging + labels: + app: redis-sentinel + environment: staging + platform: goodgo +data: + sentinel.conf: | + sentinel resolve-hostnames yes + sentinel announce-hostnames yes + sentinel monitor goodgo-master redis-master-0.redis-master.staging.svc.cluster.local 6379 2 + sentinel auth-pass goodgo-master $(REDIS_PASSWORD) + sentinel down-after-milliseconds goodgo-master 5000 + sentinel failover-timeout goodgo-master 60000 + sentinel parallel-syncs goodgo-master 1 + replica.conf: | + replicaof redis-master-0.redis-master.staging.svc.cluster.local 6379 + masterauth $(REDIS_PASSWORD) + requirepass $(REDIS_PASSWORD) + maxmemory 256mb + maxmemory-policy allkeys-lru + appendonly yes + +--- +# ============================================================================= +# StatefulSet — Redis Master (1 replica) +# ============================================================================= +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-master + namespace: staging + labels: + app: redis-master + role: master + environment: staging + platform: goodgo + tier: infrastructure +spec: + serviceName: redis-master + replicas: 1 + selector: + matchLabels: + app: redis-master + role: master + template: + metadata: + labels: + app: redis-master + role: master + environment: staging + spec: + containers: + - name: redis + image: redis:7-alpine + command: + - redis-server + - "--requirepass" + - "$(REDIS_PASSWORD)" + - "--maxmemory" + - "256mb" + - "--maxmemory-policy" + - "allkeys-lru" + - "--appendonly" + - "yes" + ports: + - containerPort: 6379 + protocol: TCP + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: goodgo-secrets + key: Redis__Password + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "250m" + livenessProbe: + exec: + command: + - sh + - -c + - redis-cli -a "$REDIS_PASSWORD" ping + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - sh + - -c + - redis-cli -a "$REDIS_PASSWORD" ping + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + volumeMounts: + - name: redis-master-data + mountPath: /data + volumeClaimTemplates: + - metadata: + name: redis-master-data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + +--- +# Service — Redis Master (headless for StatefulSet DNS) +apiVersion: v1 +kind: Service +metadata: + name: redis-master + namespace: staging + labels: + app: redis-master + role: master + environment: staging +spec: + clusterIP: None + selector: + app: redis-master + role: master + ports: + - name: redis + port: 6379 + targetPort: 6379 + +--- +# Service — Redis write endpoint (used by apps) +apiVersion: v1 +kind: Service +metadata: + name: redis + namespace: staging + labels: + app: redis-master + role: master + environment: staging +spec: + selector: + app: redis-master + role: master + ports: + - name: redis + port: 6379 + targetPort: 6379 + type: ClusterIP + +--- +# ============================================================================= +# StatefulSet — Redis Replicas (2 replicas for HA) +# ============================================================================= +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-replica + namespace: staging + labels: + app: redis-replica + role: replica + environment: staging + platform: goodgo + tier: infrastructure +spec: + serviceName: redis-replica + replicas: 2 + selector: + matchLabels: + app: redis-replica + role: replica + template: + metadata: + labels: + app: redis-replica + role: replica + environment: staging + spec: + initContainers: + - name: init-replica-conf + image: busybox:1.36 + command: + - sh + - -c + - | + sed "s/\$(REDIS_PASSWORD)/$REDIS_PASSWORD/g" /config/replica.conf > /data/redis.conf + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: goodgo-secrets + key: Redis__Password + volumeMounts: + - name: redis-config + mountPath: /config + - name: redis-replica-data + mountPath: /data + containers: + - name: redis + image: redis:7-alpine + command: + - redis-server + - /data/redis.conf + ports: + - containerPort: 6379 + protocol: TCP + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: goodgo-secrets + key: Redis__Password + resources: + requests: + memory: "128Mi" + cpu: "100m" + limits: + memory: "256Mi" + cpu: "250m" + livenessProbe: + exec: + command: + - sh + - -c + - redis-cli -a "$REDIS_PASSWORD" ping + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - sh + - -c + - redis-cli -a "$REDIS_PASSWORD" ping + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + volumeMounts: + - name: redis-replica-data + mountPath: /data + volumes: + - name: redis-config + configMap: + name: redis-sentinel-config + volumeClaimTemplates: + - metadata: + name: redis-replica-data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 1Gi + +--- +# Service — Redis Replicas (headless) +apiVersion: v1 +kind: Service +metadata: + name: redis-replica + namespace: staging + labels: + app: redis-replica + role: replica + environment: staging +spec: + clusterIP: None + selector: + app: redis-replica + role: replica + ports: + - name: redis + port: 6379 + targetPort: 6379 + +--- +# ============================================================================= +# StatefulSet — Redis Sentinel (3 nodes — quorum = 2) +# ============================================================================= +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: redis-sentinel + namespace: staging + labels: + app: redis-sentinel + role: sentinel + environment: staging + platform: goodgo + tier: infrastructure +spec: + serviceName: redis-sentinel + replicas: 3 + selector: + matchLabels: + app: redis-sentinel + role: sentinel + template: + metadata: + labels: + app: redis-sentinel + role: sentinel + environment: staging + spec: + initContainers: + - name: init-sentinel-conf + image: busybox:1.36 + command: + - sh + - -c + - | + sed "s/\$(REDIS_PASSWORD)/$REDIS_PASSWORD/g" /config/sentinel.conf > /data/sentinel.conf + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: goodgo-secrets + key: Redis__Password + volumeMounts: + - name: sentinel-config + mountPath: /config + - name: sentinel-data + mountPath: /data + containers: + - name: sentinel + image: redis:7-alpine + command: + - redis-sentinel + - /data/sentinel.conf + ports: + - containerPort: 26379 + protocol: TCP + env: + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: goodgo-secrets + key: Redis__Password + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + livenessProbe: + exec: + command: + - redis-cli + - -p + - "26379" + - ping + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + exec: + command: + - redis-cli + - -p + - "26379" + - ping + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + volumeMounts: + - name: sentinel-data + mountPath: /data + volumes: + - name: sentinel-config + configMap: + name: redis-sentinel-config + volumeClaimTemplates: + - metadata: + name: sentinel-data + spec: + accessModes: ["ReadWriteOnce"] + resources: + requests: + storage: 100Mi + +--- +# Service — Redis Sentinel (headless + ClusterIP) +apiVersion: v1 +kind: Service +metadata: + name: redis-sentinel + namespace: staging + labels: + app: redis-sentinel + role: sentinel + environment: staging +spec: + selector: + app: redis-sentinel + role: sentinel + ports: + - name: sentinel + port: 26379 + targetPort: 26379 + type: ClusterIP