mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
feat: GKE production deployment — CNPG + GCS backups + Google Workspace SSO
Add complete GKE deployment stack: - deploy/gcp/gke/: namespace, CloudNativePG cluster (premium-rwo, 100Gi), GCS barman-cloud backup with Workload Identity SA, ArgoCD OIDC ConfigMap, GitLab OmniAuth secret template, knoe-auth Google OIDC secret template - etc/init_cnpg_gke.sh: provision GCS buckets, GCP SA, WI binding, CNPG operator - etc/init_argocd.sh: apply argocd-oidc-cm.yaml when PLATFORM_DOMAIN/FRONTDOOR_HOST set - etc/init_gitlab.sh: inject OmniAuth openid_connect block when FRONTDOOR_HOST set - prole-auth-deployment.yaml: add GOOGLE_OIDC_ENABLED env vars (all optional) - supabase/helm/prole-supabase/values-gke.yaml: GoTrue Google OAuth overlay - knoe/core/prod_config.py: add backupProvider, workloadIdentitySA, googleClientId/Secret/HostedDomain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
68e4a5418c
commit
620bdd25eb
58
deploy/gcp/gke/argocd-oidc-cm.yaml
Normal file
58
deploy/gcp/gke/argocd-oidc-cm.yaml
Normal file
@ -0,0 +1,58 @@
|
||||
---
|
||||
# Patch argocd-cm to enable Google Workspace SSO via knoe-auth OIDC issuer.
|
||||
# Applied by etc/init_argocd.sh --mode k8s after ArgoCD is installed.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cm
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
data:
|
||||
# Set url to your ArgoCD external hostname
|
||||
url: "https://argocd.${PLATFORM_DOMAIN}"
|
||||
oidc.config: |
|
||||
name: "Google Workspace"
|
||||
issuer: "https://${FRONTDOOR_HOST}/auth"
|
||||
clientID: argocd
|
||||
clientSecret: $oidc.argocd.clientSecret
|
||||
requestedScopes:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
- groups
|
||||
requestedIDTokenClaims:
|
||||
groups:
|
||||
essential: true
|
||||
|
||||
---
|
||||
# RBAC: map Google Workspace admin email to ArgoCD admin role.
|
||||
# Add additional group/email → role mappings under policy.csv.
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-rbac-cm
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-rbac-cm
|
||||
app.kubernetes.io/part-of: argocd
|
||||
data:
|
||||
policy.default: role:readonly
|
||||
policy.csv: |
|
||||
g, ${BOOTSTRAP_ADMIN_EMAIL}, role:admin
|
||||
|
||||
---
|
||||
# Secret referenced by oidc.config above ($oidc.argocd.clientSecret).
|
||||
# Populated by the installer from the OIDC Client Secret field.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: argocd-secret
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-secret
|
||||
app.kubernetes.io/part-of: argocd
|
||||
type: Opaque
|
||||
stringData:
|
||||
oidc.argocd.clientSecret: "" # set by installer
|
||||
30
deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml
Normal file
30
deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: gitlab-google-oidc
|
||||
namespace: gitlab
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: prole-installer
|
||||
# GitLab OmniAuth provider config for OpenID Connect via knoe-auth.
|
||||
# See: https://docs.gitlab.com/ee/administration/auth/oidc.html
|
||||
# Applied by etc/init_gitlab.sh --mode k8s
|
||||
stringData:
|
||||
provider: |
|
||||
name: openid_connect
|
||||
label: "Google Workspace"
|
||||
icon: "https://www.google.com/favicon.ico"
|
||||
args:
|
||||
name: openid_connect
|
||||
scope:
|
||||
- openid
|
||||
- profile
|
||||
- email
|
||||
response_type: code
|
||||
issuer: "https://${FRONTDOOR_HOST}/auth"
|
||||
client_auth_method: query
|
||||
uid_field: email
|
||||
send_scope_to_token_endpoint: false
|
||||
client_options:
|
||||
identifier: "" # set to OIDC client_id
|
||||
secret: "" # set to OIDC client_secret
|
||||
redirect_uri: "https://git.${PLATFORM_DOMAIN}/users/auth/openid_connect/callback"
|
||||
14
deploy/gcp/gke/knoe-auth-google-oidc-secret.example.yaml
Normal file
14
deploy/gcp/gke/knoe-auth-google-oidc-secret.example.yaml
Normal file
@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: knoe-auth-google-oidc
|
||||
namespace: knoe-system
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: prole-installer
|
||||
# Fill in client_id and client_secret from your Google Cloud Console OAuth 2.0 credentials.
|
||||
# Create at: https://console.cloud.google.com/apis/credentials
|
||||
# Authorized redirect URI: https://<frontdoor-host>/auth/callback/google
|
||||
stringData:
|
||||
client_id: ""
|
||||
client_secret: ""
|
||||
hosted_domain: "" # restrict to a Google Workspace domain, e.g. "yourdomain.com"; blank = any Google account
|
||||
27
deploy/gcp/gke/knoe-db-backup-gcs.yaml
Normal file
27
deploy/gcp/gke/knoe-db-backup-gcs.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
# Kubernetes ServiceAccount bound to the GCP service account via Workload Identity.
|
||||
# The CNPG operator uses this SA to authenticate against GCS without a static key.
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: cnpg-backup-sa
|
||||
namespace: ecosystem-0
|
||||
annotations:
|
||||
# Set by etc/init_cnpg_gke.sh — substituted at deploy time
|
||||
iam.gke.io/gcp-service-account: "cnpg-backup@${GCP_PROJECT_ID}.iam.gserviceaccount.com"
|
||||
|
||||
---
|
||||
# Daily scheduled backup at 02:00 UTC.
|
||||
# Backup data lands in gs://knoe-0-backups/ (WAL is streamed continuously by CNPG).
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: ScheduledBackup
|
||||
metadata:
|
||||
name: knoe-db-daily
|
||||
namespace: ecosystem-0
|
||||
spec:
|
||||
schedule: "0 2 * * *"
|
||||
backupOwnerReference: self
|
||||
cluster:
|
||||
name: knoe-db
|
||||
method: barmanObjectStore
|
||||
immediate: false
|
||||
105
deploy/gcp/gke/knoe-db.yaml
Normal file
105
deploy/gcp/gke/knoe-db.yaml
Normal file
@ -0,0 +1,105 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: knoe-db
|
||||
namespace: ecosystem-0
|
||||
spec:
|
||||
instances: 3
|
||||
# Image pulled from GCP Artifact Registry — set ARTIFACT_REGISTRY in conf/prod/gcp.cfg
|
||||
# e.g. us-central1-docker.pkg.dev/<project>/prole/knoe-db:18-140
|
||||
imageName: "${ARTIFACT_REGISTRY}/knoe-db:18-140"
|
||||
postgresUID: 100
|
||||
postgresGID: 101
|
||||
maxSyncReplicas: 1
|
||||
|
||||
affinity:
|
||||
enablePodAntiAffinity: true
|
||||
podAntiAffinityType: preferred
|
||||
topologyKey: topology.kubernetes.io/zone # GKE zone-aware spreading
|
||||
|
||||
postgresql:
|
||||
parameters:
|
||||
shared_buffers: 256MB
|
||||
pg_stat_statements.max: '10000'
|
||||
pg_stat_statements.track: all
|
||||
shared_preload_libraries:
|
||||
- pg_stat_statements
|
||||
- pg_tde
|
||||
pg_hba:
|
||||
- local all postgres trust
|
||||
- local all prole scram-sha-256
|
||||
- host all postgres all scram-sha-256
|
||||
- host prole knoe-db all scram-sha-256
|
||||
- host all all all scram-sha-256
|
||||
- hostssl prole knoe-db all scram-sha-256
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: knoe-db
|
||||
owner: prole
|
||||
localeCollate: 'en_US.utf8'
|
||||
localeCType: 'en_US.utf8'
|
||||
secret:
|
||||
name: knoe-db-user
|
||||
postInitTemplateSQL:
|
||||
- CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
|
||||
postInitSQL:
|
||||
- CREATE EXTENSION IF NOT EXISTS pg_tde;
|
||||
- CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO prole;
|
||||
- CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
- CREATE EXTENSION IF NOT EXISTS postgis_topology;
|
||||
- CREATE EXTENSION IF NOT EXISTS vector;
|
||||
- CREATE EXTENSION IF NOT EXISTS tds_fdw;
|
||||
- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO prole;
|
||||
- GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA topology TO prole;
|
||||
- CREATE SCHEMA IF NOT EXISTS storage;
|
||||
- CREATE SCHEMA IF NOT EXISTS graphql_public;
|
||||
- CREATE ROLE anon NOLOGIN;
|
||||
- CREATE ROLE authenticator LOGIN;
|
||||
- GRANT USAGE ON SCHEMA public TO anon;
|
||||
- GRANT USAGE ON SCHEMA storage TO anon;
|
||||
- GRANT USAGE ON SCHEMA graphql_public TO anon;
|
||||
- GRANT anon TO authenticator;
|
||||
|
||||
certificates:
|
||||
serverTLSSecret: knoe-db-tls
|
||||
serverCASecret: knoe-db-ca
|
||||
|
||||
enableSuperuserAccess: true
|
||||
|
||||
# GCS backup via Workload Identity — see knoe-db-backup-gcs.yaml
|
||||
backup:
|
||||
barmanObjectStore:
|
||||
destinationPath: "gs://knoe-0-backups/"
|
||||
googleCredentials:
|
||||
applicationCredentials:
|
||||
name: "" # empty = Workload Identity; no static key needed
|
||||
wal:
|
||||
compression: gzip
|
||||
data:
|
||||
compression: gzip
|
||||
retentionPolicy: "14d"
|
||||
|
||||
storage:
|
||||
size: 100Gi
|
||||
pvcTemplate:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 100Gi
|
||||
storageClassName: premium-rwo # GCP SSD persistent disk (pd-ssd)
|
||||
|
||||
walStorage:
|
||||
size: 20Gi
|
||||
pvcTemplate:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 20Gi
|
||||
storageClassName: premium-rwo
|
||||
|
||||
monitoring:
|
||||
enablePodMonitor: true # GKE managed Prometheus
|
||||
6
deploy/gcp/gke/namespace.yaml
Normal file
6
deploy/gcp/gke/namespace.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ecosystem-0
|
||||
labels:
|
||||
app.kubernetes.io/managed-by: prole-installer
|
||||
26
deploy/gcp/gke/workload-identity.yaml
Normal file
26
deploy/gcp/gke/workload-identity.yaml
Normal file
@ -0,0 +1,26 @@
|
||||
# Workload Identity binding documentation.
|
||||
#
|
||||
# This file is NOT applied with kubectl — it documents the gcloud commands
|
||||
# executed by etc/init_cnpg_gke.sh to establish the Workload Identity link
|
||||
# between the Kubernetes ServiceAccount (cnpg-backup-sa) and the GCP
|
||||
# service account (cnpg-backup@<project>.iam.gserviceaccount.com).
|
||||
#
|
||||
# Commands run by init_cnpg_gke.sh:
|
||||
#
|
||||
# gcloud iam service-accounts create cnpg-backup \
|
||||
# --display-name="CNPG GCS Backup" \
|
||||
# --project=$GCP_PROJECT_ID
|
||||
#
|
||||
# gsutil iam ch \
|
||||
# serviceAccount:cnpg-backup@$GCP_PROJECT_ID.iam.gserviceaccount.com:objectAdmin \
|
||||
# gs://knoe-0-backups
|
||||
#
|
||||
# gcloud iam service-accounts add-iam-policy-binding \
|
||||
# cnpg-backup@$GCP_PROJECT_ID.iam.gserviceaccount.com \
|
||||
# --role=roles/iam.workloadIdentityUser \
|
||||
# --member="serviceAccount:$GCP_PROJECT_ID.svc.id.goog[ecosystem-0/cnpg-backup-sa]"
|
||||
#
|
||||
# kubectl annotate serviceaccount cnpg-backup-sa \
|
||||
# -n ecosystem-0 \
|
||||
# iam.gke.io/gcp-service-account=cnpg-backup@$GCP_PROJECT_ID.iam.gserviceaccount.com \
|
||||
# --overwrite
|
||||
@ -155,6 +155,37 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: prole-auth-kerberos
|
||||
key: realm
|
||||
# ── Google Workspace OIDC (primary auth for GKE/prod) ──────────
|
||||
# Secret created by installer from Auth+Routing tab OIDC fields.
|
||||
# Template: deploy/gcp/gke/knoe-auth-google-oidc-secret.example.yaml
|
||||
- name: GOOGLE_OIDC_ENABLED
|
||||
value: "true"
|
||||
- name: GOOGLE_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knoe-auth-google-oidc
|
||||
key: client_id
|
||||
optional: true # Kerberos-only deployments can omit this secret
|
||||
- name: GOOGLE_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knoe-auth-google-oidc
|
||||
key: client_secret
|
||||
optional: true
|
||||
- name: GOOGLE_HOSTED_DOMAIN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: knoe-auth-google-oidc
|
||||
key: hosted_domain
|
||||
optional: true # blank = any Google account; set to restrict to Workspace domain
|
||||
- name: OIDC_ISSUER_URL
|
||||
value: "https://knoe-auth.knoe-system.svc.cluster.local:8080"
|
||||
- name: OIDC_BASE_URL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: prole-platform-config
|
||||
key: frontdoorHost
|
||||
optional: true
|
||||
volumeMounts:
|
||||
- name: keytab
|
||||
mountPath: /etc/prole/keytabs
|
||||
|
||||
@ -32,6 +32,9 @@ fi
|
||||
|
||||
ACTION="$COMMON_CORE_ACTION"
|
||||
|
||||
REPO_ROOT="${PROLE_HOME:-$(cd "$SCRIPT_DIR/.." && pwd)}"
|
||||
GKE_MANIFEST_DIR="${GKE_MANIFEST_DIR:-$REPO_ROOT/deploy/gcp/gke}"
|
||||
|
||||
if [[ -d "$SCRIPT_DIR/../k8s/argocd" ]]; then
|
||||
ARGOCD_MANIFEST_DIR="$SCRIPT_DIR/../k8s/argocd"
|
||||
elif [[ -n "${PROLE_HOME:-}" && -d "$PROLE_HOME/k8s/argocd" ]]; then
|
||||
@ -52,6 +55,11 @@ ARGOCD_PORT_FORWARD_LOCAL=${ARGOCD_PORT_FORWARD_LOCAL:-8081}
|
||||
ARGOCD_PORT_FORWARD_REMOTE=${ARGOCD_PORT_FORWARD_REMOTE:-80}
|
||||
ARGOCD_NODE_SELECTOR=${ARGOCD_NODE_SELECTOR:-}
|
||||
|
||||
# ── Google Workspace OIDC (applied when all three vars are set) ───────────────
|
||||
PLATFORM_DOMAIN="${PLATFORM_DOMAIN:-}"
|
||||
FRONTDOOR_HOST="${FRONTDOOR_HOST:-}"
|
||||
BOOTSTRAP_ADMIN_EMAIL="${BOOTSTRAP_ADMIN_EMAIL:-}"
|
||||
|
||||
ensure_tools() {
|
||||
command -v kubectl >/dev/null || { echo "Missing required tool: kubectl" >&2; exit 1; }
|
||||
}
|
||||
@ -122,6 +130,24 @@ apply_argocd() {
|
||||
kubectl rollout status statefulset/argocd-application-controller -n "$ARGOCD_NAMESPACE" --timeout=${ROLLOUT_TIMEOUT:-300s} || true
|
||||
}
|
||||
|
||||
apply_argocd_oidc_config() {
|
||||
local cm_file="$GKE_MANIFEST_DIR/argocd-oidc-cm.yaml"
|
||||
if [[ -z "$PLATFORM_DOMAIN" || -z "$FRONTDOOR_HOST" || -z "$BOOTSTRAP_ADMIN_EMAIL" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ! -f "$cm_file" ]]; then
|
||||
echo "WARN: ArgoCD OIDC config not found at $cm_file; skipping OIDC patch." >&2
|
||||
return 0
|
||||
fi
|
||||
echo "Applying ArgoCD OIDC config (issuer=https://${FRONTDOOR_HOST}/auth) ..."
|
||||
PLATFORM_DOMAIN="$PLATFORM_DOMAIN" \
|
||||
FRONTDOOR_HOST="$FRONTDOOR_HOST" \
|
||||
BOOTSTRAP_ADMIN_EMAIL="$BOOTSTRAP_ADMIN_EMAIL" \
|
||||
envsubst < "$cm_file" \
|
||||
| kubectl apply --server-side --force-conflicts \
|
||||
--field-manager=prole-installer -f -
|
||||
}
|
||||
|
||||
rollout_restart_argocd() {
|
||||
kubectl -n "$ARGOCD_NAMESPACE" rollout restart deploy/argocd-server deploy/argocd-repo-server \
|
||||
deploy/argocd-dex-server deploy/argocd-applicationset-controller deploy/argocd-notifications-controller \
|
||||
@ -144,6 +170,7 @@ case "$ACTION" in
|
||||
ensure_tools
|
||||
ensure_namespace
|
||||
apply_argocd
|
||||
apply_argocd_oidc_config
|
||||
prole_register_port_forward "argocd" "$ARGOCD_NAMESPACE" "svc/${ARGOCD_SERVER_SERVICE}" \
|
||||
"$ARGOCD_PORT_FORWARD_LOCAL" "$ARGOCD_PORT_FORWARD_REMOTE" "0.0.0.0" "TCP" "ArgoCD"
|
||||
;;
|
||||
@ -152,6 +179,7 @@ case "$ACTION" in
|
||||
ensure_namespace
|
||||
apply_argocd
|
||||
rollout_restart_argocd
|
||||
apply_argocd_oidc_config
|
||||
prole_register_port_forward "argocd" "$ARGOCD_NAMESPACE" "svc/${ARGOCD_SERVER_SERVICE}" \
|
||||
"$ARGOCD_PORT_FORWARD_LOCAL" "$ARGOCD_PORT_FORWARD_REMOTE" "0.0.0.0" "TCP" "ArgoCD"
|
||||
;;
|
||||
|
||||
138
etc/init_cnpg_gke.sh
Executable file
138
etc/init_cnpg_gke.sh
Executable file
@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env bash
|
||||
# init_cnpg_gke.sh
|
||||
# Provision CloudNativePG on GKE with GCS backup via Workload Identity.
|
||||
#
|
||||
# Usage:
|
||||
# ./etc/init_cnpg_gke.sh [--project PROJECT_ID] [--region REGION] [--cluster CLUSTER_NAME]
|
||||
#
|
||||
# Env vars (override flags):
|
||||
# GCP_PROJECT_ID, GCP_REGION, GKE_CLUSTER, GCS_BACKUP_BUCKET,
|
||||
# CNPG_NAMESPACE, ARTIFACT_REGISTRY, CLOUDSDK_AUTH_ACCESS_TOKEN
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
GKE_MANIFEST_DIR="$REPO_ROOT/deploy/gcp/gke"
|
||||
|
||||
# ── Defaults ────────────────────────────────────────────────────────────────
|
||||
GCP_PROJECT_ID="${GCP_PROJECT_ID:-}"
|
||||
GCP_REGION="${GCP_REGION:-us-central1}"
|
||||
GKE_CLUSTER="${GKE_CLUSTER:-knoe-dev-0}"
|
||||
GCS_BACKUP_BUCKET="${GCS_BACKUP_BUCKET:-knoe-0-backups}"
|
||||
GCS_WAL_BUCKET="${GCS_WAL_BUCKET:-knoe-0-wal}"
|
||||
CNPG_NAMESPACE="${CNPG_NAMESPACE:-ecosystem-0}"
|
||||
CNPG_BACKUP_SA="${CNPG_BACKUP_SA:-cnpg-backup}"
|
||||
ARTIFACT_REGISTRY="${ARTIFACT_REGISTRY:-}"
|
||||
|
||||
# ── Argument parsing ─────────────────────────────────────────────────────────
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--project) GCP_PROJECT_ID="$2"; shift 2 ;;
|
||||
--region) GCP_REGION="$2"; shift 2 ;;
|
||||
--cluster) GKE_CLUSTER="$2"; shift 2 ;;
|
||||
--bucket) GCS_BACKUP_BUCKET="$2"; shift 2 ;;
|
||||
--namespace) CNPG_NAMESPACE="$2"; shift 2 ;;
|
||||
--registry) ARTIFACT_REGISTRY="$2"; shift 2 ;;
|
||||
*) echo "Unknown argument: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -z "$GCP_PROJECT_ID" ]] && {
|
||||
# Try to read from gcp.cfg
|
||||
GCP_CFG="$REPO_ROOT/conf/prod/gcp.cfg"
|
||||
if [[ -f "$GCP_CFG" ]]; then
|
||||
GCP_PROJECT_ID=$(grep '^project_id' "$GCP_CFG" | sed 's/.*=\s*"\?\([^"]*\)"\?.*/\1/' | tr -d '[:space:]')
|
||||
fi
|
||||
}
|
||||
[[ -z "$GCP_PROJECT_ID" ]] && { echo "ERROR: GCP_PROJECT_ID not set and conf/prod/gcp.cfg not found." >&2; exit 1; }
|
||||
|
||||
GCP_SA_EMAIL="${CNPG_BACKUP_SA}@${GCP_PROJECT_ID}.iam.gserviceaccount.com"
|
||||
|
||||
log() { echo "[init_cnpg_gke] $*"; }
|
||||
|
||||
# ── Tool checks ──────────────────────────────────────────────────────────────
|
||||
for tool in gcloud kubectl gsutil; do
|
||||
command -v "$tool" >/dev/null || { echo "ERROR: $tool not found in PATH." >&2; exit 1; }
|
||||
done
|
||||
|
||||
# ── 1. Acquire kubeconfig ────────────────────────────────────────────────────
|
||||
log "Fetching kubeconfig for $GKE_CLUSTER in $GCP_REGION ..."
|
||||
gcloud container clusters get-credentials "$GKE_CLUSTER" \
|
||||
--project "$GCP_PROJECT_ID" \
|
||||
--region "$GCP_REGION"
|
||||
|
||||
# ── 2. Create GCS buckets ────────────────────────────────────────────────────
|
||||
for bucket in "$GCS_BACKUP_BUCKET" "$GCS_WAL_BUCKET"; do
|
||||
if gsutil ls -b "gs://$bucket" >/dev/null 2>&1; then
|
||||
log "Bucket gs://$bucket already exists."
|
||||
else
|
||||
log "Creating bucket gs://$bucket ..."
|
||||
gsutil mb -p "$GCP_PROJECT_ID" -l "$GCP_REGION" "gs://$bucket"
|
||||
gsutil versioning set on "gs://$bucket"
|
||||
gsutil lifecycle set /dev/stdin "gs://$bucket" <<EOF
|
||||
{"rule":[{"action":{"type":"Delete"},"condition":{"age":30}}]}
|
||||
EOF
|
||||
fi
|
||||
done
|
||||
|
||||
# ── 3. Create GCP service account ────────────────────────────────────────────
|
||||
if gcloud iam service-accounts describe "$GCP_SA_EMAIL" --project "$GCP_PROJECT_ID" >/dev/null 2>&1; then
|
||||
log "Service account $GCP_SA_EMAIL already exists."
|
||||
else
|
||||
log "Creating service account $GCP_SA_EMAIL ..."
|
||||
gcloud iam service-accounts create "$CNPG_BACKUP_SA" \
|
||||
--display-name="CNPG GCS Backup" \
|
||||
--project="$GCP_PROJECT_ID"
|
||||
fi
|
||||
|
||||
# ── 4. Grant storage access ───────────────────────────────────────────────────
|
||||
log "Granting objectAdmin on backup buckets to $GCP_SA_EMAIL ..."
|
||||
for bucket in "$GCS_BACKUP_BUCKET" "$GCS_WAL_BUCKET"; do
|
||||
gsutil iam ch \
|
||||
"serviceAccount:${GCP_SA_EMAIL}:objectAdmin" \
|
||||
"gs://$bucket"
|
||||
done
|
||||
|
||||
# ── 5. Bind Workload Identity ─────────────────────────────────────────────────
|
||||
log "Binding Workload Identity ..."
|
||||
gcloud iam service-accounts add-iam-policy-binding "$GCP_SA_EMAIL" \
|
||||
--role=roles/iam.workloadIdentityUser \
|
||||
--member="serviceAccount:${GCP_PROJECT_ID}.svc.id.goog[${CNPG_NAMESPACE}/cnpg-backup-sa]" \
|
||||
--project="$GCP_PROJECT_ID"
|
||||
|
||||
# ── 6. Apply namespace ────────────────────────────────────────────────────────
|
||||
log "Applying namespace $CNPG_NAMESPACE ..."
|
||||
kubectl apply -f "$GKE_MANIFEST_DIR/namespace.yaml"
|
||||
|
||||
# ── 7. Install CNPG operator (if not present) ─────────────────────────────────
|
||||
if ! kubectl get crd clusters.postgresql.cnpg.io >/dev/null 2>&1; then
|
||||
log "Installing CloudNativePG operator ..."
|
||||
kubectl apply --server-side -f \
|
||||
https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/main/releases/cnpg-1.24.0.yaml
|
||||
log "Waiting for CNPG operator to be ready ..."
|
||||
kubectl rollout status deployment/cnpg-controller-manager -n cnpg-system --timeout=120s
|
||||
else
|
||||
log "CNPG operator already installed."
|
||||
fi
|
||||
|
||||
# ── 8. Apply ServiceAccount + annotate with WI ───────────────────────────────
|
||||
log "Applying CNPG backup ServiceAccount with Workload Identity annotation ..."
|
||||
GCP_PROJECT_ID="$GCP_PROJECT_ID" envsubst < "$GKE_MANIFEST_DIR/knoe-db-backup-gcs.yaml" \
|
||||
| kubectl apply -f -
|
||||
|
||||
kubectl annotate serviceaccount cnpg-backup-sa \
|
||||
-n "$CNPG_NAMESPACE" \
|
||||
"iam.gke.io/gcp-service-account=${GCP_SA_EMAIL}" \
|
||||
--overwrite
|
||||
|
||||
# ── 9. Apply CNPG cluster ────────────────────────────────────────────────────
|
||||
log "Applying knoe-db CNPG cluster ..."
|
||||
ARTIFACT_REGISTRY="${ARTIFACT_REGISTRY}" envsubst < "$GKE_MANIFEST_DIR/knoe-db.yaml" \
|
||||
| kubectl apply -f -
|
||||
|
||||
log ""
|
||||
log "Done. Monitor with:"
|
||||
log " kubectl -n $CNPG_NAMESPACE get cluster knoe-db"
|
||||
log " kubectl -n $CNPG_NAMESPACE get pods -l cnpg.io/cluster=knoe-db"
|
||||
log " kubectl -n $CNPG_NAMESPACE get scheduledbackup"
|
||||
@ -96,6 +96,9 @@ DB_PORT="${GITLAB_DB_PORT:-5432}"
|
||||
|
||||
# GitLab domain (reuses the git.prole.org shared hostname)
|
||||
GITLAB_DOMAIN="${GITLAB_DOMAIN:-git.prole.org}"
|
||||
|
||||
# Google Workspace OIDC via knoe-auth — set FRONTDOOR_HOST to enable
|
||||
FRONTDOOR_HOST="${FRONTDOOR_HOST:-}"
|
||||
GITLAB_RELEASE="gitlab"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -320,6 +323,22 @@ fi
|
||||
# ---------------------------------------------------------------------------
|
||||
# Create the GitLab CR (operator reconciles this into the full deployment)
|
||||
# ---------------------------------------------------------------------------
|
||||
# ---------------------------------------------------------------------------
|
||||
# Apply gitlab-google-oidc secret (OIDC provider config for OmniAuth)
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ -n "$FRONTDOOR_HOST" ]]; then
|
||||
PLATFORM_DOMAIN="${PLATFORM_DOMAIN:-}"
|
||||
oidc_secret_tmpl="${SCRIPT_DIR}/../deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml"
|
||||
if [[ -f "$oidc_secret_tmpl" ]]; then
|
||||
log "Applying gitlab-google-oidc secret (issuer=https://${FRONTDOOR_HOST}/auth) ..."
|
||||
FRONTDOOR_HOST="$FRONTDOOR_HOST" PLATFORM_DOMAIN="$PLATFORM_DOMAIN" \
|
||||
envsubst < "$oidc_secret_tmpl" | kubectl apply -n "$NAMESPACE" -f - >/dev/null || \
|
||||
warn "Could not apply gitlab-google-oidc secret; create it manually from deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml"
|
||||
else
|
||||
warn "gitlab-google-oidc-secret.example.yaml not found; skipping OIDC secret (create it manually)."
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Applying GitLab CR (domain=${GITLAB_DOMAIN}, db=${GITLAB_DB_NAME}@${DB_HOST})..."
|
||||
|
||||
kubectl apply -f - <<EOF
|
||||
@ -356,6 +375,20 @@ spec:
|
||||
# the gitlab namespace but can reference services in knoe-db.
|
||||
serviceAccount:
|
||||
create: true
|
||||
$(if [[ -n "$FRONTDOOR_HOST" ]]; then
|
||||
cat <<OMNIAUTH
|
||||
appConfig:
|
||||
omniauth:
|
||||
enabled: true
|
||||
autoSignInWithProvider: openid_connect
|
||||
allowSingleSignOn:
|
||||
- openid_connect
|
||||
blockAutoCreatedUsers: false
|
||||
providers:
|
||||
- secret: gitlab-google-oidc
|
||||
key: provider
|
||||
OMNIAUTH
|
||||
fi)
|
||||
# Disable bundled sub-charts — we provide our own data stores
|
||||
postgresql:
|
||||
install: false
|
||||
|
||||
@ -49,6 +49,8 @@ class ProdBackupsConfig:
|
||||
backupBucket: str = "knoe-0-backups"
|
||||
walBucket: str = "knoe-0-wal"
|
||||
retentionDays: int = 14
|
||||
backupProvider: str = "gcs" # "gcs" | "garage"
|
||||
workloadIdentitySA: str = "cnpg-backup"
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@ -58,6 +60,9 @@ class ProdAuthConfig:
|
||||
clientId: str = "secretref://google-oidc-client-id"
|
||||
clientSecret: str = "secretref://google-oidc-client-secret"
|
||||
bootstrapAdminEmail: str = "admin@knoey.com"
|
||||
googleClientId: str = "" # transient — not persisted to prole.cfg
|
||||
googleClientSecret: str = "" # transient — not persisted to prole.cfg
|
||||
googleHostedDomain: str = "" # blank = any Google account; set to restrict Workspace domain
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@ -212,6 +217,10 @@ def build_config(payload: dict[str, Any] | None = None) -> KnoeProductionConfig:
|
||||
cfg.backups.retentionDays = _coerce_int(
|
||||
backups.get("retentionDays", cfg.backups.retentionDays), cfg.backups.retentionDays
|
||||
)
|
||||
cfg.backups.backupProvider = _str_or_empty(backups.get("backupProvider")) or cfg.backups.backupProvider
|
||||
cfg.backups.workloadIdentitySA = (
|
||||
_str_or_empty(backups.get("workloadIdentitySA")) or cfg.backups.workloadIdentitySA
|
||||
)
|
||||
|
||||
cfg.auth.provider = _str_or_empty(auth.get("provider")) or cfg.auth.provider
|
||||
cfg.auth.issuer = _str_or_empty(auth.get("issuer")) or cfg.auth.issuer
|
||||
@ -220,6 +229,9 @@ def build_config(payload: dict[str, Any] | None = None) -> KnoeProductionConfig:
|
||||
cfg.auth.bootstrapAdminEmail = (
|
||||
_str_or_empty(auth.get("bootstrapAdminEmail")) or cfg.auth.bootstrapAdminEmail
|
||||
)
|
||||
cfg.auth.googleClientId = _str_or_empty(auth.get("googleClientId"))
|
||||
cfg.auth.googleClientSecret = _str_or_empty(auth.get("googleClientSecret"))
|
||||
cfg.auth.googleHostedDomain = _str_or_empty(auth.get("googleHostedDomain"))
|
||||
|
||||
cfg.routing.frontdoorHost = _str_or_empty(routing.get("frontdoorHost")) or cfg.routing.frontdoorHost
|
||||
cfg.routing.platformDomain = _str_or_empty(routing.get("platformDomain")) or cfg.routing.platformDomain
|
||||
@ -354,10 +366,13 @@ def opentofu_vars(config: KnoeProductionConfig) -> dict[str, Any]:
|
||||
"backup_bucket": config.backups.backupBucket,
|
||||
"wal_bucket": config.backups.walBucket,
|
||||
"backup_retention_days": config.backups.retentionDays,
|
||||
"backup_provider": config.backups.backupProvider,
|
||||
"workload_identity_sa": config.backups.workloadIdentitySA,
|
||||
"oidc_issuer": config.auth.issuer,
|
||||
"oidc_client_id_ref": config.auth.clientId,
|
||||
"oidc_client_secret_ref": config.auth.clientSecret,
|
||||
"bootstrap_admin_email": config.auth.bootstrapAdminEmail,
|
||||
"google_hosted_domain": config.auth.googleHostedDomain,
|
||||
"frontdoor_host": config.routing.frontdoorHost,
|
||||
"platform_domain": config.routing.platformDomain,
|
||||
"tls_mode": config.routing.tlsMode,
|
||||
|
||||
46
supabase/helm/prole-supabase/values-gke.yaml
Normal file
46
supabase/helm/prole-supabase/values-gke.yaml
Normal file
@ -0,0 +1,46 @@
|
||||
# GKE production values overlay for prole-supabase Helm chart.
|
||||
#
|
||||
# Apply on top of values.yaml:
|
||||
# helm upgrade --install prole-supabase ./supabase/helm/prole-supabase \
|
||||
# --values supabase/helm/prole-supabase/values.yaml \
|
||||
# --values supabase/helm/prole-supabase/values-gke.yaml \
|
||||
# --set environment.auth.GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID="$CLIENT_ID" \
|
||||
# --set environment.auth.GOTRUE_EXTERNAL_GOOGLE_SECRET="$CLIENT_SECRET" \
|
||||
# --set environment.auth.GOTRUE_SITE_URL="https://$FRONTDOOR_HOST" \
|
||||
# --set environment.auth.GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI="https://$FRONTDOOR_HOST/auth/v1/callback" \
|
||||
# --set environment.auth.GOTRUE_URI_ALLOW_LIST="https://$FRONTDOOR_HOST/**"
|
||||
#
|
||||
# Or set FRONTDOOR_HOST, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET in environment
|
||||
# and let init_supabase.sh --mode k8s substitute them automatically.
|
||||
|
||||
# ── Storage: use GCS instead of bundled MinIO ───────────────────────────────
|
||||
# When CNPG Workload Identity is active the pod SA already has storage access;
|
||||
# set storage driver to gcs and disable minio.
|
||||
deployment:
|
||||
minio:
|
||||
enabled: false
|
||||
|
||||
# ── GoTrue: enable Google OAuth via knoe-auth OIDC broker ───────────────────
|
||||
environment:
|
||||
auth:
|
||||
# Override base URL to the GKE frontdoor
|
||||
API_EXTERNAL_URL: "" # set to https://<frontdoor-host>/auth/v1 at deploy time
|
||||
GOTRUE_SITE_URL: "" # set to https://<frontdoor-host>
|
||||
GOTRUE_URI_ALLOW_LIST: "" # set to https://<frontdoor-host>/**
|
||||
|
||||
# Google OAuth2 — client ID and secret from knoe-auth-google-oidc secret
|
||||
GOTRUE_EXTERNAL_GOOGLE_ENABLED: "true"
|
||||
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID: "" # injected at deploy time
|
||||
GOTRUE_EXTERNAL_GOOGLE_SECRET: "" # injected at deploy time
|
||||
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI: "" # set to https://<frontdoor-host>/auth/v1/callback
|
||||
|
||||
# Disable anonymous sign-in in production
|
||||
GOTRUE_EXTERNAL_ANONYMOUS_USERS_ENABLED: "false"
|
||||
|
||||
# Auto-confirm email (Google accounts are pre-verified)
|
||||
GOTRUE_MAILER_AUTOCONFIRM: "true"
|
||||
|
||||
# ── Ingress: use GKE-managed ingress class ──────────────────────────────────
|
||||
ingress:
|
||||
studio:
|
||||
className: "gce" # GKE L7 load balancer; override with "kong" if Kong is installed
|
||||
Loading…
Reference in New Issue
Block a user