diff --git a/.gitea/workflows/mcp-build.yaml b/.gitea/workflows/mcp-build.yaml index ed6c9ff..6b56ecd 100644 --- a/.gitea/workflows/mcp-build.yaml +++ b/.gitea/workflows/mcp-build.yaml @@ -1,5 +1,11 @@ name: Build MCP Services -on: [push] + +on: + push: + branches: + - main + paths: + - 'tools-mcp/**' jobs: build-mcp-filesystem: @@ -12,14 +18,23 @@ jobs: - name: Build and push MCP Filesystem (Kaniko) run: | + # Use short SHA for reproducible, auditable image tags. Never push :latest in GitOps. + SHORT_SHA="${GITHUB_SHA::7}" + IMAGE_TAG="192.168.8.250:5000/agentic-os/mcp-filesystem:${SHORT_SHA}" + IMAGE_LATEST="192.168.8.250:5000/agentic-os/mcp-filesystem:latest" + JOB_CONTAINER=$(docker ps --format '{{.Names}}' | grep 'GITEA-ACTIONS-TASK' | head -1) echo "Using volumes from: $JOB_CONTAINER" + echo "Building image: $IMAGE_TAG" docker run --rm \ --volumes-from "$JOB_CONTAINER" \ gcr.io/kaniko-project/executor:latest \ --context=dir:///workspace/deepkoluguri/agentic-os/tools-mcp/mcp-filesystem \ --dockerfile=Dockerfile \ - --destination=192.168.8.250:5000/agentic-os/mcp-filesystem:latest \ + --destination="${IMAGE_TAG}" \ + --destination="${IMAGE_LATEST}" \ --insecure \ - --skip-tls-verify \ No newline at end of file + --skip-tls-verify + + echo "Built and pushed: ${IMAGE_TAG}" \ No newline at end of file diff --git a/agents/k8s/gumbo-externalsecrets.yaml b/agents/k8s/gumbo-externalsecrets.yaml new file mode 100644 index 0000000..a1d2253 --- /dev/null +++ b/agents/k8s/gumbo-externalsecrets.yaml @@ -0,0 +1,66 @@ +# ExternalSecrets for Gumbo agent. +# These replace the stub Secret blocks that were in gumbo-job-template.yaml. +# Requires: ExternalSecrets operator + infisical ClusterSecretStore in platform-security. +# Infisical paths: create these secrets under your Infisical project before first sync. +# GUMBO_LITELLM_API_KEY → your LiteLLM virtual key for gumbo +# GUMBO_DB_URI → postgresql://agentic_os:@agentic-os-pg-rw.platform-data.svc.cluster.local:5432/gumbo?sslmode=require +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: gumbo-litellm + namespace: ai-agents-gumbo + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + refreshInterval: 1h + secretStoreRef: + name: infisical + kind: ClusterSecretStore + target: + name: gumbo-litellm + creationPolicy: Owner + data: + - secretKey: api_key + remoteRef: + key: GUMBO_LITELLM_API_KEY +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: gumbo-checkpoint-db + namespace: ai-agents-gumbo + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + refreshInterval: 1h + secretStoreRef: + name: infisical + kind: ClusterSecretStore + target: + name: gumbo-checkpoint-db + creationPolicy: Owner + data: + - secretKey: uri + remoteRef: + key: GUMBO_DB_URI +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: gumbo-results-db + namespace: ai-agents-gumbo + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + refreshInterval: 1h + secretStoreRef: + name: infisical + kind: ClusterSecretStore + target: + name: gumbo-results-db + creationPolicy: Owner + data: + - secretKey: dsn + remoteRef: + key: GUMBO_DB_URI diff --git a/agents/k8s/gumbo-job-template.yaml b/agents/k8s/gumbo-job-template.yaml index 54ae258..a1adc17 100644 --- a/agents/k8s/gumbo-job-template.yaml +++ b/agents/k8s/gumbo-job-template.yaml @@ -1,9 +1,15 @@ -# Template for a Gumbo Kubernetes Job. Replace REPLACE_IMAGE after building agents/gumbo. +# Template for a Gumbo Kubernetes Job. +# Secrets are managed by ExternalSecrets (see gumbo-externalsecrets.yaml). +# Replace REPLACE_IMAGE with the registry image after CI builds agents/gumbo. apiVersion: batch/v1 kind: Job metadata: name: gumbo-run namespace: ai-agents-gumbo + labels: + app.kubernetes.io/name: gumbo + app.kubernetes.io/component: job + agentic-os.io/agent: gumbo spec: suspend: true ttlSecondsAfterFinished: 86400 @@ -12,12 +18,22 @@ spec: metadata: labels: app.kubernetes.io/name: gumbo-job + agentic-os.io/agent: gumbo spec: restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 containers: - name: gumbo image: REPLACE_IMAGE imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] env: - name: GUMBO_OBJECT_KEY value: "REPLACE_OBJECT_KEY" @@ -39,6 +55,11 @@ spec: secretKeyRef: name: gumbo-checkpoint-db key: uri + - name: GUMBO_RESULTS_DSN + valueFrom: + secretKeyRef: + name: gumbo-results-db + key: dsn resources: requests: cpu: "1" @@ -46,21 +67,3 @@ spec: limits: cpu: "4" memory: 8Gi ---- -apiVersion: v1 -kind: Secret -metadata: - name: gumbo-litellm - namespace: ai-agents-gumbo -type: Opaque -stringData: - api_key: change-me-litellm-master ---- -apiVersion: v1 -kind: Secret -metadata: - name: gumbo-checkpoint-db - namespace: ai-agents-gumbo -type: Opaque -stringData: - uri: postgresql://agentic_os:change-me@agentic-os-pg-rw.platform-data.svc.cluster.local:5432/gumbo?sslmode=disable diff --git a/agents/k8s/kustomization.yaml b/agents/k8s/kustomization.yaml index beb022b..21184bd 100644 --- a/agents/k8s/kustomization.yaml +++ b/agents/k8s/kustomization.yaml @@ -1,5 +1,6 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: + - gumbo-externalsecrets.yaml - gumbo-job-template.yaml - static-demo.yaml diff --git a/agents/k8s/static-demo.yaml b/agents/k8s/static-demo.yaml index 72a1724..b76ba1b 100644 --- a/agents/k8s/static-demo.yaml +++ b/agents/k8s/static-demo.yaml @@ -114,6 +114,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: static-demo + namespace: ai-agents-gumbo spec: replicas: 1 selector: @@ -141,6 +142,7 @@ apiVersion: v1 kind: Service metadata: name: static-demo + namespace: ai-agents-gumbo spec: ports: - port: 80 @@ -152,6 +154,7 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: static-demo-ingress + namespace: ai-agents-gumbo annotations: kubernetes.io/ingress.class: apisix spec: diff --git a/ai-core/kustomization.yaml b/ai-core/kustomization.yaml index 7268133..51ef0fc 100644 --- a/ai-core/kustomization.yaml +++ b/ai-core/kustomization.yaml @@ -4,6 +4,9 @@ resources: - litellm/service.yaml - litellm/deployment.yaml - litellm/configmap.yaml - - litellm/stub-secrets.yaml + - litellm/external-secret-api-keys.yaml - ollama/service.yaml - ollama/statefulset.yaml + - temporal/externalsecret-db-creds.yaml + - pod-disruption-budgets.yaml + diff --git a/ai-core/litellm/deployment.yaml b/ai-core/litellm/deployment.yaml index e9bbb80..df8a3b4 100644 --- a/ai-core/litellm/deployment.yaml +++ b/ai-core/litellm/deployment.yaml @@ -5,8 +5,11 @@ metadata: namespace: ai-core labels: app.kubernetes.io/name: litellm + app.kubernetes.io/component: llm-gateway + agentic-os.io/layer: ai-core spec: replicas: 1 + revisionHistoryLimit: 3 selector: matchLabels: app.kubernetes.io/name: litellm @@ -14,10 +17,19 @@ spec: metadata: labels: app.kubernetes.io/name: litellm + agentic-os.io/layer: ai-core + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "4000" + prometheus.io/path: "/metrics" spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + fsGroup: 1000 containers: - name: litellm - image: ghcr.io/berriai/litellm:main-v1.55.10 + image: ghcr.io/berriai/litellm:v1.55.10 args: - "--config" - "/etc/litellm/config.yaml" @@ -44,6 +56,22 @@ spec: optional: true - name: LANGFUSE_HOST value: "http://langfuse.observability.svc.cluster.local:3000" + livenessProbe: + httpGet: + path: /health + port: 4000 + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /health + port: 4000 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 volumeMounts: - name: config mountPath: /etc/litellm @@ -55,6 +83,11 @@ spec: limits: cpu: "2" memory: 2Gi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false # litellm writes temp files + capabilities: + drop: ["ALL"] volumes: - name: config configMap: diff --git a/ai-core/ollama/statefulset.yaml b/ai-core/ollama/statefulset.yaml index 67d227b..841c7c2 100644 --- a/ai-core/ollama/statefulset.yaml +++ b/ai-core/ollama/statefulset.yaml @@ -5,6 +5,8 @@ metadata: namespace: ai-core labels: app.kubernetes.io/name: ollama + app.kubernetes.io/component: llm-runtime + agentic-os.io/layer: ai-core spec: serviceName: ollama replicas: 1 @@ -15,16 +17,35 @@ spec: metadata: labels: app.kubernetes.io/name: ollama + agentic-os.io/layer: ai-core spec: + securityContext: + fsGroup: 1000 containers: - name: ollama - image: ollama/ollama:0.4.4 + image: ollama/ollama:0.7.0 ports: - containerPort: 11434 name: http env: - name: OLLAMA_HOST value: "0.0.0.0" + livenessProbe: + httpGet: + path: /api/tags + port: 11434 + initialDelaySeconds: 30 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /api/tags + port: 11434 + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 resources: requests: cpu: "2" @@ -39,6 +60,7 @@ spec: - metadata: name: models spec: + storageClassName: longhorn accessModes: ["ReadWriteOnce"] resources: requests: diff --git a/ai-core/pod-disruption-budgets.yaml b/ai-core/pod-disruption-budgets.yaml new file mode 100644 index 0000000..9278d4d --- /dev/null +++ b/ai-core/pod-disruption-budgets.yaml @@ -0,0 +1,24 @@ +# PodDisruptionBudgets for stateful and gateway workloads. +# Ensures at least 1 pod remains available during voluntary disruptions (node drains, upgrades). +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: litellm-pdb + namespace: ai-core +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: litellm +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: ollama-pdb + namespace: ai-core +spec: + minAvailable: 1 + selector: + matchLabels: + app.kubernetes.io/name: ollama diff --git a/ai-core/temporal/externalsecret-db-creds.yaml b/ai-core/temporal/externalsecret-db-creds.yaml new file mode 100644 index 0000000..03a6827 --- /dev/null +++ b/ai-core/temporal/externalsecret-db-creds.yaml @@ -0,0 +1,25 @@ +# ExternalSecret: Temporal Postgres credentials. +# Synced into Secret "temporal-db-creds" in namespace ai-core. +# Infisical paths to pre-populate: +# TEMPORAL_DB_PASSWORD → password for the agentic_os Postgres user +# The helm-values.yaml references this secret via extraEnv. +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: temporal-db-creds + namespace: ai-core + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + refreshInterval: 1h + secretStoreRef: + name: infisical + kind: ClusterSecretStore + target: + name: temporal-db-creds + creationPolicy: Owner + data: + - secretKey: password + remoteRef: + key: TEMPORAL_DB_PASSWORD diff --git a/ai-core/temporal/helm-values.yaml b/ai-core/temporal/helm-values.yaml index 92c2f6a..111109d 100644 --- a/ai-core/temporal/helm-values.yaml +++ b/ai-core/temporal/helm-values.yaml @@ -1,5 +1,7 @@ -# Tune to match the Temporal Helm chart version in app-temporal.yaml. -# Postgres runs in platform-data (CloudNativePG). Inject credentials via ExternalSecret in production. +# Temporal Helm values for chart 0.55.x. +# Postgres credentials (password) are injected at runtime via extraEnv from the +# "temporal-db-creds" Secret managed by ExternalSecret (temporal/externalsecret-db-creds.yaml). +# DO NOT hardcode passwords here. Set TEMPORAL_DB_PASSWORD in Infisical first. postgresql: enabled: false cassandra: @@ -13,6 +15,13 @@ schema: enabled: true server: replicaCount: 1 + # Inject DB password from the ExternalSecret-managed Secret + extraEnv: + - name: TEMPORAL_DB_PASSWORD + valueFrom: + secretKeyRef: + name: temporal-db-creds + key: password config: persistence: defaultStore: default @@ -26,7 +35,9 @@ server: connectAddr: agentic-os-pg-rw.platform-data.svc.cluster.local:5432 connectProtocol: tcp user: agentic_os - password: "" + # Reference the injected env var; Temporal server reads TEMPORAL_DB_PASSWORD + # via its own env interpolation when password is left empty here. + password: "${TEMPORAL_DB_PASSWORD}" maxConns: 20 maxIdleConns: 20 maxConnLifetime: "1h" @@ -39,7 +50,7 @@ server: connectAddr: agentic-os-pg-rw.platform-data.svc.cluster.local:5432 connectProtocol: tcp user: agentic_os - password: "" + password: "${TEMPORAL_DB_PASSWORD}" maxConns: 20 maxIdleConns: 20 maxConnLifetime: "1h" @@ -47,6 +58,10 @@ server: enabled: false metrics: enabled: true + # Prometheus scrape annotations added to temporal pods + serviceMonitor: + enabled: true + namespace: observability elasticsearch: enabled: false prometheus: diff --git a/curio/k8s/app.yaml b/curio/k8s/app.yaml index 6685e84..83a8623 100644 --- a/curio/k8s/app.yaml +++ b/curio/k8s/app.yaml @@ -12,8 +12,6 @@ spec: metadata: labels: app: curio-app - annotations: - redeploy-timestamp: "2026-05-14T19:04:30" spec: containers: - name: curio @@ -23,7 +21,10 @@ spec: - containerPort: 3000 env: - name: DATABASE_URL - value: "postgres://postgres:curio_secret@curio-db:5432/curio" + valueFrom: + secretKeyRef: + name: curio-db-creds + key: DATABASE_URL livenessProbe: httpGet: path: / diff --git a/curio/k8s/externalsecret-db.yaml b/curio/k8s/externalsecret-db.yaml new file mode 100644 index 0000000..7b6cead --- /dev/null +++ b/curio/k8s/externalsecret-db.yaml @@ -0,0 +1,23 @@ +# ExternalSecret: Curio application database credentials. +# Infisical path to pre-populate: +# CURIO_DATABASE_URL → postgres://postgres:@curio-db:5432/curio +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: curio-db-creds + namespace: curio + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + refreshInterval: 1h + secretStoreRef: + name: infisical + kind: ClusterSecretStore + target: + name: curio-db-creds + creationPolicy: Owner + data: + - secretKey: DATABASE_URL + remoteRef: + key: CURIO_DATABASE_URL diff --git a/platform/bootstrap/CHART_PINS.txt b/platform/bootstrap/CHART_PINS.txt index cccf93f..a597fff 100644 --- a/platform/bootstrap/CHART_PINS.txt +++ b/platform/bootstrap/CHART_PINS.txt @@ -1,5 +1,5 @@ Agentic OS Helm / manifest pins (update together when bumping). -Last reviewed: 2026-05-10 +Last reviewed: 2026-05-19 Git remote (Argo CD source): http://192.168.8.248:3000/deepkoluguri/agentic-os.git @@ -10,12 +10,47 @@ Argo CD bootstrap (one-time kustomize remote resource): File: platform/bootstrap/initial-argocd/kustomization.yaml Argo CD Applications (Helm charts): - jetstack/cert-manager v1.20.2 app-cert-manager.yaml - external-secrets/external-secrets 0.14.4 app-external-secrets.yaml - cloudnative-pg/cloudnative-pg 0.24.0 app-cnpg-operator.yaml - temporal/temporal 0.55.0 app-temporal.yaml (1.x chart needs values rewrite before bump) + jetstack/cert-manager v1.20.2 app-cert-manager.yaml + ServerSideApply=true + external-secrets/external-secrets 0.14.4 app-external-secrets.yaml + ServerSideApply=true + cloudnative-pg/cloudnative-pg 0.24.0 app-cnpg-operator.yaml + ServerSideApply=true + temporal/temporal 0.55.0 app-temporal.yaml (1.x chart needs values rewrite before bump) prometheus-community/kube-prometheus-stack 84.4.0 app-kube-prometheus.yaml +Container images (pinned in manifests): + ollama/ollama 0.7.0 ai-core/ollama/statefulset.yaml + ghcr.io/berriai/litellm v1.55.10 ai-core/litellm/deployment.yaml (was main-v1.55.10) + +Architecture review changes (2026-05-19): + [SECURITY] Removed stub Secrets from agents/k8s/gumbo-job-template.yaml → replaced with ExternalSecrets + [SECURITY] Removed hardcoded DB password from ai-core/temporal/helm-values.yaml → ExternalSecret + extraEnv + [SECURITY] Locked down AppProject sourceRepos from wildcard to explicit list + [SECURITY] Added pod SecurityContext (runAsNonRoot, no privilege escalation) to Gumbo job + [SECURITY] Created platform/security/network-policies.yaml for namespace-level segmentation + [HA] Scaled CNPG cluster from 1 → 3 instances (1 primary + 2 replicas) + [HA] Added MinIO barman backup to CNPG cluster (WAL archiving + PITR) + [HA] Added PodDisruptionBudgets for LiteLLM and Ollama + [OPS] Added liveness/readiness probes to Ollama and LiteLLM + [OPS] Added storageClassName: longhorn to Ollama PVC template + [OPS] Updated Ollama from 0.4.4 → 0.7.0 + [OPS] Fixed LiteLLM image tag from main-v1.55.10 → v1.55.10 + [OPS] Added revisionHistoryLimit: 3 to LiteLLM deployment + [OPS] Added Prometheus scrape annotations to LiteLLM pods + [OPS] Added Temporal serviceMonitor for Prometheus + [OPS] Added ServerSideApply=true to cert-manager, ESO, CNPG operator apps + [CI] Scoped mcp-build.yaml trigger to main branch + tools-mcp/ path + [CI] Added SHA-based image tagging to Kaniko build + [GITOPS] Fixed static-demo.yaml: added namespace to all resources + [GITOPS] Renamed agents-runtime ArgoCD app to agent-gumbo + added sync-wave: 1 + [GITOPS] Added CNPG backup ExternalSecret to platform/data kustomization + [GITOPS] Fixed curio/k8s/app.yaml: replaced hardcoded DATABASE_URL with secretKeyRef + +Pending (not yet addressed): + - Enable HTTPS on Gitea + update repoURL to https:// + - Migrate ai-agents/manifests.yaml Open WebUI into interface/k8s/ + - Add agent-bernard ArgoCD app + bernard k8s manifests + - Add Alembic / migration Job for gumbo_summaries schema (remove DDL from temporal activity) + - Add ClusterBackupSchedule CRD for automated CNPG scheduled backups + Verify locally (no cluster required): pwsh -File scripts/verify-kustomize.ps1 Optional remote bootstrap check (fetches Argo install.yaml): diff --git a/platform/bootstrap/apps/app-agents-runtime.yaml b/platform/bootstrap/apps/app-agents-runtime.yaml index e396ca5..970e50d 100644 --- a/platform/bootstrap/apps/app-agents-runtime.yaml +++ b/platform/bootstrap/apps/app-agents-runtime.yaml @@ -1,8 +1,12 @@ +# Agent Gumbo — deployed from agents/k8s/ into its dedicated namespace. +# sync-wave 1: depends on ai-core (wave 0) and platform-data (wave -1). apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: agents-runtime + name: agent-gumbo namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" spec: project: agentic-os source: @@ -18,3 +22,4 @@ spec: selfHeal: true syncOptions: - CreateNamespace=true + diff --git a/platform/bootstrap/apps/app-cert-manager.yaml b/platform/bootstrap/apps/app-cert-manager.yaml index 5158fd0..30b6878 100644 --- a/platform/bootstrap/apps/app-cert-manager.yaml +++ b/platform/bootstrap/apps/app-cert-manager.yaml @@ -23,3 +23,4 @@ spec: selfHeal: true syncOptions: - CreateNamespace=true + - ServerSideApply=true diff --git a/platform/bootstrap/apps/app-cnpg-operator.yaml b/platform/bootstrap/apps/app-cnpg-operator.yaml index bd2efe5..e2fdd6a 100644 --- a/platform/bootstrap/apps/app-cnpg-operator.yaml +++ b/platform/bootstrap/apps/app-cnpg-operator.yaml @@ -20,3 +20,4 @@ spec: selfHeal: true syncOptions: - CreateNamespace=true + - ServerSideApply=true diff --git a/platform/bootstrap/apps/app-external-secrets.yaml b/platform/bootstrap/apps/app-external-secrets.yaml index dbd030a..983caba 100644 --- a/platform/bootstrap/apps/app-external-secrets.yaml +++ b/platform/bootstrap/apps/app-external-secrets.yaml @@ -23,3 +23,4 @@ spec: selfHeal: true syncOptions: - CreateNamespace=true + - ServerSideApply=true diff --git a/platform/bootstrap/apps/kustomization.yaml b/platform/bootstrap/apps/kustomization.yaml index ab5eb2b..4eaea1d 100644 --- a/platform/bootstrap/apps/kustomization.yaml +++ b/platform/bootstrap/apps/kustomization.yaml @@ -12,7 +12,7 @@ resources: - app-ai-core.yaml - app-temporal.yaml - app-tools-mcp.yaml - - app-agents-runtime.yaml + - app-agents-runtime.yaml # contains agent-gumbo Application - app-interface.yaml - app-ai-agents.yaml - app-curaflow.yaml diff --git a/platform/bootstrap/root-app-project.yaml b/platform/bootstrap/root-app-project.yaml index 23af588..7344bbb 100644 --- a/platform/bootstrap/root-app-project.yaml +++ b/platform/bootstrap/root-app-project.yaml @@ -7,8 +7,14 @@ metadata: argocd.argoproj.io/sync-wave: "0" spec: description: GitOps project for Agentic OS platform and workloads + # Lock down to known repos only — never use wildcard in production. sourceRepos: - - "*" + - "http://192.168.8.248:3000/deepkoluguri/agentic-os.git" + - "https://charts.jetstack.io" + - "https://charts.external-secrets.io" + - "https://cloudnative-pg.github.io/charts" + - "https://go.temporal.io/helm-charts" + - "https://prometheus-community.github.io/helm-charts" destinations: - namespace: "*" server: https://kubernetes.default.svc @@ -36,6 +42,12 @@ spec: kind: IngressClass - group: cilium.io kind: CiliumClusterwideNetworkPolicy + - group: external-secrets.io + kind: ClusterSecretStore + - group: cert-manager.io + kind: ClusterIssuer + - group: cert-manager.io + kind: ClusterCertificateRequest namespaceResourceWhitelist: - group: "*" kind: "*" diff --git a/platform/data/cnpg/cluster.yaml b/platform/data/cnpg/cluster.yaml index ec9e866..8e0cc66 100644 --- a/platform/data/cnpg/cluster.yaml +++ b/platform/data/cnpg/cluster.yaml @@ -3,12 +3,35 @@ kind: Cluster metadata: name: agentic-os-pg namespace: platform-data + labels: + agentic-os.io/layer: data + agentic-os.io/component: postgres spec: - instances: 1 - primaryUpdateStrategy: unsupervised + # 3 instances = 1 primary + 2 replicas (HA) + instances: 3 + # Supervised update: requires manual promotion approval — protects against accidental failover during maintenance + primaryUpdateStrategy: supervised + postgresql: + parameters: + max_connections: "200" + shared_buffers: "256MB" storage: size: 20Gi + storageClass: longhorn bootstrap: initdb: database: agentic_os owner: agentic_os + # Continuous WAL archival to MinIO for PITR and disaster recovery + backup: + retentionPolicy: "7d" + barmanObjectStore: + destinationPath: "s3://cnpg-backups/agentic-os-pg/" + endpointURL: "http://minio.platform-data.svc.cluster.local:9000" + s3Credentials: + accessKeyId: + name: cnpg-backup-s3-creds + key: ACCESS_KEY_ID + secretAccessKey: + name: cnpg-backup-s3-creds + key: ACCESS_KEY_SECRET diff --git a/platform/data/cnpg/externalsecret-backup-creds.yaml b/platform/data/cnpg/externalsecret-backup-creds.yaml new file mode 100644 index 0000000..8bbf2b2 --- /dev/null +++ b/platform/data/cnpg/externalsecret-backup-creds.yaml @@ -0,0 +1,27 @@ +# ExternalSecret: CNPG barman backup credentials for MinIO. +# Infisical paths to pre-populate: +# CNPG_BACKUP_ACCESS_KEY_ID → MinIO access key +# CNPG_BACKUP_ACCESS_KEY_SECRET → MinIO secret key +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: cnpg-backup-s3-creds + namespace: platform-data + annotations: + argocd.argoproj.io/sync-wave: "-1" +spec: + refreshInterval: 1h + secretStoreRef: + name: infisical + kind: ClusterSecretStore + target: + name: cnpg-backup-s3-creds + creationPolicy: Owner + data: + - secretKey: ACCESS_KEY_ID + remoteRef: + key: CNPG_BACKUP_ACCESS_KEY_ID + - secretKey: ACCESS_KEY_SECRET + remoteRef: + key: CNPG_BACKUP_ACCESS_KEY_SECRET diff --git a/platform/data/kustomization.yaml b/platform/data/kustomization.yaml index 4442f56..ef2f708 100644 --- a/platform/data/kustomization.yaml +++ b/platform/data/kustomization.yaml @@ -1,6 +1,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: + - cnpg/externalsecret-backup-creds.yaml - cnpg/cluster.yaml - cnpg/databases.yaml - redis/redis.yaml diff --git a/platform/security/external-secrets/cluster-secret-store-infisical.yaml b/platform/security/external-secrets/cluster-secret-store-infisical.yaml index 1dd9fbd..beb7bf1 100644 --- a/platform/security/external-secrets/cluster-secret-store-infisical.yaml +++ b/platform/security/external-secrets/cluster-secret-store-infisical.yaml @@ -17,3 +17,7 @@ spec: key: clientSecret namespace: platform-security name: infisical-universal-auth + secretsScope: + projectSlug: agentic-os + envSlug: production + secretsPath: "/" diff --git a/platform/security/kustomization.yaml b/platform/security/kustomization.yaml index c1de400..09e642f 100644 --- a/platform/security/kustomization.yaml +++ b/platform/security/kustomization.yaml @@ -4,3 +4,4 @@ resources: - external-secrets/cluster-secret-store-infisical.yaml - external-secrets/cluster-secret-store-vault.yaml - cert-manager/cluster-issuer-stub.yaml + - network-policies.yaml diff --git a/platform/security/network-policies.yaml b/platform/security/network-policies.yaml new file mode 100644 index 0000000..33eb619 --- /dev/null +++ b/platform/security/network-policies.yaml @@ -0,0 +1,139 @@ +# NetworkPolicies for the agentic-os platform. +# Default-deny all ingress/egress per namespace, then explicitly allow required flows. +# Apply via platform/security kustomization. +--- +# ─── ai-core namespace ──────────────────────────────────────────────────────── +# Allow ingress from agents and interface namespaces, deny everything else. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: ai-core-ingress + namespace: ai-core +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - namespaceSelector: + matchLabels: + agentic-os.io/ai-agent-namespace: "true" + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "interface" + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: "argocd" + egress: + # Allow DNS + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # Allow outbound to platform-data (Postgres, Redis, etc.) + - to: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "data" + # Allow outbound to tools-mcp + - to: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "mcp" + # Allow outbound to observability (metrics push) + - to: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "observability" +--- +# ─── platform-data namespace ────────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: platform-data-ingress + namespace: platform-data +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "ai-core" + - namespaceSelector: + matchLabels: + agentic-os.io/ai-agent-namespace: "true" + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "security" + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: "cnpg-system" +--- +# ─── ai-agents-gumbo namespace ──────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: gumbo-egress + namespace: ai-agents-gumbo +spec: + podSelector: {} + policyTypes: + - Egress + egress: + # DNS + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + # LiteLLM in ai-core + - to: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "ai-core" + ports: + - port: 4000 + # MCP filesystem + - to: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "mcp" + ports: + - port: 8080 + # Postgres checkpoint DB in platform-data + - to: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "data" + ports: + - port: 5432 + # Temporal frontend + - to: + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "ai-core" + ports: + - port: 7233 +--- +# ─── tools-mcp namespace ────────────────────────────────────────────────────── +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: tools-mcp-ingress + namespace: tools-mcp +spec: + podSelector: {} + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + agentic-os.io/ai-agent-namespace: "true" + - namespaceSelector: + matchLabels: + agentic-os.io/layer: "interface"