diff --git a/.forgejo/scripts/deploy-prd.sh b/.forgejo/scripts/deploy-prd.sh new file mode 100644 index 0000000..efc8972 --- /dev/null +++ b/.forgejo/scripts/deploy-prd.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +NAMESPACE=go-insane-prd +REGISTRY=nexus.urpi.be +CERT_MANAGER_VERSION=v1.19.1 +LOCAL_PATH_VERSION=v0.0.37 +LOCK_NAME=forgejo-prd-deploy-lock +if [[ $# -lt 4 ]]; then echo "Usage: $0 [helm args...]" >&2; exit 2; fi +CHART=$1; RELEASE=$2; IMAGE_NAME=$3; IMAGE_TAG=$4; shift 4 +: "${KUBECONFIG_PRD:?Set the Forgejo secret KUBECONFIG_PRD to the PRD kubeconfig contents}" +: "${NEXUS_USERNAME:?Set the Forgejo secret NEXUS_USERNAME}" +: "${NEXUS_PASSWORD:?Set the Forgejo secret NEXUS_PASSWORD}" +KUBECONFIG="${RUNNER_TEMP:-/tmp}/kubeconfig-prd-${GITHUB_RUN_ID:-$$}" +umask 077; printf '%s\n' "$KUBECONFIG_PRD" > "$KUBECONFIG"; export KUBECONFIG +LOCK_ACQUIRED=false +cleanup() { if [[ "$LOCK_ACQUIRED" == true ]]; then kubectl -n "$NAMESPACE" delete configmap "$LOCK_NAME" --ignore-not-found --wait=false >/dev/null 2>&1 || true; fi; rm -f "$KUBECONFIG"; } +trap cleanup EXIT +secret_value() { + local name=$1 key=$2 encoded + encoded="$(kubectl -n "$NAMESPACE" get secret "$name" -o go-template="{{index .data \"${key}\"}}" 2>/dev/null || true)" + [[ -z "$encoded" || "$encoded" == "" ]] && return 0 + printf '%s' "$encoded" | base64 -d 2>/dev/null || true +} +kubectl get --raw=/readyz >/dev/null +if [[ "$RELEASE" == go-loose ]] && ! kubectl -n "$NAMESPACE" get secret go-loose-wildcard-tls >/dev/null 2>&1; then + echo "Required go-loose-wildcard-tls Secret is missing; provision the tenant wildcard certificate before bootstrap/deploy." >&2 + exit 1 +fi +kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f - +for attempt in {1..360}; do if kubectl -n "$NAMESPACE" create configmap "$LOCK_NAME" --from-literal=owner="${GITHUB_REPOSITORY:-local}:${GITHUB_RUN_ID:-$$}" >/dev/null 2>&1; then LOCK_ACQUIRED=true; break; fi; sleep 5; done +if [[ "$LOCK_ACQUIRED" != true ]]; then echo "Timed out waiting for another go-insane-prd deploy to release its lock." >&2; exit 1; fi +kubectl -n "$NAMESPACE" create secret docker-registry nexus-registry --docker-server="$REGISTRY" --docker-username="$NEXUS_USERNAME" --docker-password="$NEXUS_PASSWORD" --dry-run=client -o yaml | kubectl apply -f - +DEFAULT_STORAGE_CLASS="$(kubectl get storageclass -o=jsonpath='{range .items[*]}{.metadata.name}{" "}{.metadata.annotations.storageclass\.kubernetes\.io/is-default-class}{"\n"}{end}' | awk '$2 == "true" { print $1; exit }')" +if [[ -z "$DEFAULT_STORAGE_CLASS" ]]; then + if ! kubectl get storageclass local-path >/dev/null 2>&1 || ! kubectl -n local-path-storage get deployment local-path-provisioner >/dev/null 2>&1; then kubectl apply -f "https://raw.githubusercontent.com/rancher/local-path-provisioner/${LOCAL_PATH_VERSION}/deploy/local-path-storage.yaml"; kubectl -n local-path-storage rollout status deployment/local-path-provisioner --timeout=5m; fi + kubectl annotate storageclass local-path storageclass.kubernetes.io/is-default-class=true --overwrite +fi +if ! kubectl get ingressclass nginx >/dev/null 2>&1; then + helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx --force-update + helm repo update ingress-nginx + helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx --namespace ingress-nginx --create-namespace --set controller.service.type=NodePort --set controller.hostPort.enabled=true --set controller.kind=DaemonSet --set controller.admissionWebhooks.enabled=false --wait --timeout 10m +fi +if ! kubectl get crd clusterissuers.cert-manager.io >/dev/null 2>&1 || ! kubectl -n cert-manager get deployment cert-manager >/dev/null 2>&1; then + kubectl apply --validate=false -f "https://github.com/cert-manager/cert-manager/releases/download/${CERT_MANAGER_VERSION}/cert-manager.yaml" + kubectl -n cert-manager rollout status deployment/cert-manager --timeout=5m + kubectl -n cert-manager rollout status deployment/cert-manager-cainjector --timeout=5m + kubectl -n cert-manager rollout status deployment/cert-manager-webhook --timeout=5m +fi +if ! kubectl get clusterissuer letsencrypt-prod >/dev/null 2>&1; then + kubectl apply -f - <<'ISSUER' +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: admin@urpi.be + privateKeySecretRef: + name: letsencrypt-prod + solvers: + - http01: + ingress: + class: nginx +ISSUER +fi +kubectl wait --for=condition=Ready clusterissuer/letsencrypt-prod --timeout=2m +[[ -d "$CHART" ]] || { echo "Helm chart not found: $CHART" >&2; exit 1; } +if [[ -f "$CHART/Chart.lock" ]]; then helm dependency build "$CHART"; fi +HELM_ARGS=(--namespace "$NAMESPACE" --create-namespace) +if [[ "$RELEASE" != go-guess ]]; then HELM_ARGS+=(--set-string "image.repository=${REGISTRY}/docker-hosted/${IMAGE_NAME}" --set-string "image.tag=${IMAGE_TAG}"); fi +[[ ! -f "$CHART/values-prd.yaml" ]] || HELM_ARGS+=(--values "$CHART/values-prd.yaml") +case "$RELEASE" in + go-loose) + db_password="$(secret_value go-loose-runtime postgresql-password)" + [[ -n "$db_password" ]] || db_password="$(secret_value go-loose-postgresql postgres-password)" + [[ -n "$db_password" ]] || db_password="$(secret_value go-loose-postgresql password)" + if [[ -z "$db_password" ]] && kubectl -n "$NAMESPACE" get pvc -o name | grep -Eq 'go-loose.*postgresql|data-go-loose'; then + echo "Existing go-loose database storage has no recoverable password secret; refusing to rotate its credentials." >&2 + exit 1 + fi + [[ -n "$db_password" ]] || db_password="$(openssl rand -hex 24)" + session_secret="$(secret_value go-loose-runtime session-secret)" + [[ -n "$session_secret" ]] || session_secret="$(secret_value go-loose-env GO_LOOSE_SESSION_SECRET)" + [[ -n "$session_secret" ]] || session_secret="$(openssl rand -hex 32)" + database_url="postgres://goloose:${db_password}@go-loose-postgresql:5432/goloose?sslmode=disable" + kubectl -n "$NAMESPACE" create secret generic go-loose-runtime \ + --from-literal=postgresql-password="$db_password" \ + --from-literal=postgres-password="$db_password" \ + --from-literal=database-url="$database_url" \ + --from-literal=session-secret="$session_secret" \ + --dry-run=client -o yaml | kubectl apply -f - + ;; + go-mad) + if ! kubectl -n "$NAMESPACE" get secret go-mad-runtime >/dev/null 2>&1; then + db_password="$(secret_value go-mad-postgresql password)"; [[ -n "$db_password" ]] || db_password="$(secret_value go-mad-postgresql postgres-password)" + if [[ -z "$db_password" ]] && kubectl -n "$NAMESPACE" get pvc -o name | grep -Eq 'go-mad.*postgresql|data-go-mad'; then echo "Existing go-mad database storage has no recoverable password secret; refusing to rotate its credentials." >&2; exit 1; fi + [[ -n "$db_password" ]] || db_password="$(openssl rand -hex 24)" + kubectl -n "$NAMESPACE" create secret generic go-mad-runtime --from-literal=postgresql-password="$db_password" --from-literal=jwt-secret="$(openssl rand -hex 32)" + fi + db_password="$(secret_value go-mad-runtime postgresql-password)"; jwt_secret="$(secret_value go-mad-runtime jwt-secret)" + HELM_ARGS+=(--set-string "postgresql.auth.password=${db_password}" --set-string "go-mad.jwtSecret=${jwt_secret}") + ;; + go-first) + if ! kubectl -n "$NAMESPACE" get secret go-first-runtime >/dev/null 2>&1; then + kubectl -n "$NAMESPACE" create secret generic go-first-runtime --from-literal=jwt-secret="$(openssl rand -hex 32)" + fi + ;; + go-places) + : "${GO_LOOSE_JWT_SECRET:?Set Forgejo secret GO_LOOSE_JWT_SECRET to the same signing key used by go-loose}" + kubectl -n "$NAMESPACE" create secret generic go-loose-jwt --from-literal=secret="$GO_LOOSE_JWT_SECRET" --dry-run=client -o yaml | kubectl apply -f - + ;; + go-bananas) + : "${GO_BANANAS_OIDC_CLIENT_SECRET:?Set Forgejo secret GO_BANANAS_OIDC_CLIENT_SECRET}" + session_secret="$(secret_value go-bananas-auth session-secret)" + if [[ -z "$session_secret" ]]; then session_secret="$(openssl rand -hex 32)"; fi + kubectl -n "$NAMESPACE" create secret generic go-bananas-auth --from-literal=session-secret="$session_secret" --from-literal=oidc-client-secret="$GO_BANANAS_OIDC_CLIENT_SECRET" --dry-run=client -o yaml | kubectl apply -f - + ;; +esac +helm upgrade --install "$RELEASE" "$CHART" "${HELM_ARGS[@]}" "$@" --wait --timeout 15m +kubectl -n "$NAMESPACE" get deployments,statefulsets,pods,services,ingresses diff --git a/.forgejo/workflows/ci-cd.yaml b/.forgejo/workflows/ci-cd.yaml index 3f513a2..b8bb112 100644 --- a/.forgejo/workflows/ci-cd.yaml +++ b/.forgejo/workflows/ci-cd.yaml @@ -1,58 +1,41 @@ -name: CI/CD +name: Build, test, publish, deploy on: push: - branches: [main, develop] - tags: ['v*'] pull_request: - branches: [main, develop] + workflow_dispatch: env: - REGISTRY: nexus-registry.urpi.be + GO_VERSION: "1.27.1" + REGISTRY: nexus.urpi.be IMAGE_NAME: docker-hosted/go-loose - GO_VERSION: '1.27.1' jobs: - build-and-test: - name: Build and Test + build-test: runs-on: ubuntu-latest - permissions: - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version-file: go.mod cache: true - - - name: Download dependencies - run: go mod download - - - name: Build - run: go build ./... - - - name: Run unit tests - run: go test -race ./... -count=1 - - - name: Run vet - run: go vet ./... - - - name: Check formatting + - name: Build and test Go service run: | - if [ "$(gofmt -l . | grep -v vendor | grep -v dist | wc -l)" -gt 0 ]; then - echo "Code is not formatted:" - gofmt -l . | grep -v vendor | grep -v dist - exit 1 - fi + go mod download + go vet ./... + go test -race ./... -count=1 + go build ./... + - uses: azure/setup-helm@v4 + with: + version: v3.18.6 + - name: Validate production chart + run: | + helm dependency build deploy/helm/go-loose + helm lint deploy/helm/go-loose -f deploy/helm/go-loose/values-prd.yaml integration-test: - name: Integration Tests runs-on: ubuntu-latest - needs: build-and-test + needs: build-test services: postgres: image: postgres:17-alpine @@ -60,151 +43,70 @@ jobs: POSTGRES_DB: goloose POSTGRES_USER: goloose POSTGRES_PASSWORD: goloose - ports: ['5432:5432'] + ports: [5432:5432] options: >- --health-cmd="pg_isready -U goloose -d goloose" --health-interval=5s --health-timeout=3s --health-retries=10 - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: ${{ env.GO_VERSION }} + go-version-file: go.mod cache: true - - - name: Download dependencies - run: go mod download - - - name: Create test databases + - name: Run database-backed tests + env: + PGPASSWORD: goloose + GO_LOOSE_TEST_DATABASE_URL: postgres://goloose:goloose@localhost:5432/goloose_test?sslmode=disable + GO_LOOSE_TEST_SERVER_DATABASE_URL: postgres://goloose:goloose@localhost:5432/goloose_server_test?sslmode=disable + GO_LOOSE_BEHAVIOUR_DATABASE_URL: postgres://goloose:goloose@localhost:5432/goloose_behaviour_test?sslmode=disable run: | sleep 5 createdb -h localhost -U goloose goloose_test || true createdb -h localhost -U goloose goloose_server_test || true createdb -h localhost -U goloose goloose_behaviour_test || true - - - name: Run integration tests - env: - GO_LOOSE_TEST_DATABASE_URL: postgres://goloose:goloose@localhost:5432/goloose_test?sslmode=disable - GO_LOOSE_TEST_SERVER_DATABASE_URL: postgres://goloose:goloose@localhost:5432/goloose_server_test?sslmode=disable - GO_LOOSE_BEHAVIOUR_DATABASE_URL: postgres://goloose:goloose@localhost:5432/goloose_behaviour_test?sslmode=disable - run: | go test -race ./internal/store/ ./internal/server/ -count=1 GO_LOOSE_BEHAVIOUR=1 go test ./internal/behaviour/ -count=1 -v - docker: - name: Build and Push Docker Image + publish: + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') + needs: [build-test, integration-test] runs-on: ubuntu-latest - needs: [build-and-test, integration-test] - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - permissions: - contents: read - packages: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Nexus Registry - uses: docker/login-action@v3 + - uses: actions/checkout@v4 + - uses: docker/setup-buildx-action@v3 + - uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ secrets.NEXUS_USERNAME }} password: ${{ secrets.NEXUS_PASSWORD }} - - - name: Extract metadata for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha - - - name: Build and push Docker image - uses: docker/build-push-action@v5 + - uses: docker/build-push-action@v6 with: context: . + file: Dockerfile push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - -# deploy-tst: -# name: Deploy to TST -# runs-on: ubuntu-latest -# needs: docker -# if: github.event_name == 'push' && github.ref == 'refs/heads/develop' -# permissions: -# contents: read -# id-token: write -# environment: tst -# -# steps: -# - name: Checkout repository -# uses: actions/checkout@v4 -# -# - name: Set up Helm -# uses: azure/setup-helm@v4 -# with: -# version: 'v3.14.0' -# -# - name: Set up kubectl -# uses: azure/k8s-set-context@v4 -# with: -# kubeconfig: ${{ secrets.KUBECONFIG_TST }} -# -# - name: Deploy to TST with Helm -# run: | -# helm upgrade --install go-loose ./deploy/helm/go-loose \ -# --namespace go-loose \ -# --create-namespace \ -# --set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \ -# --set image.tag=${{ github.sha }} \ -# --set image.pullSecrets[0].name=nexus-registry \ -# --wait --timeout 5m + tags: | + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest deploy-prd: - name: Deploy to PRD + if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || (github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main') + needs: publish runs-on: ubuntu-latest - needs: docker - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - permissions: - contents: read - id-token: write - environment: prd - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Helm - uses: azure/setup-helm@v4 + - uses: actions/checkout@v4 + - uses: azure/setup-helm@v4 with: - version: 'v3.14.0' - - - name: Set up kubectl - uses: azure/k8s-set-context@v4 + version: v3.18.6 + - uses: azure/setup-kubectl@v4 with: - kubeconfig: ${{ secrets.KUBECONFIG_PRD }} - - - name: Deploy to PRD with Helm + version: v1.32.2 + - name: Bootstrap resources and deploy go-loose + env: + KUBECONFIG_PRD: ${{ secrets.KUBECONFIG_PRD }} + NEXUS_USERNAME: ${{ secrets.NEXUS_USERNAME }} + NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} run: | - helm upgrade --install go-loose ./deploy/helm/go-loose \ - --namespace go-loose \ - --create-namespace \ - --values ./deploy/helm/go-loose/values-prd.yaml \ - --set image.repository=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} \ - --set image.tag=${{ github.ref_name }} \ - --wait --timeout 5m \ No newline at end of file + set -Eeuo pipefail + bash .forgejo/scripts/deploy-prd.sh deploy/helm/go-loose go-loose go-loose "${{ github.sha }}" diff --git a/deploy/helm/go-loose/Chart.lock b/deploy/helm/go-loose/Chart.lock new file mode 100644 index 0000000..10b4a65 --- /dev/null +++ b/deploy/helm/go-loose/Chart.lock @@ -0,0 +1,6 @@ +dependencies: + - name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 16.7.27 +digest: sha256:e8b999a3aaad6328a13d0c666207650897b69c61367c947d363b2acbe0ea1b42 +generated: "2026-10-04T19:38:06.343578353+02:00" diff --git a/deploy/helm/go-loose/Chart.yaml b/deploy/helm/go-loose/Chart.yaml index 3f6e183..0c18efa 100644 --- a/deploy/helm/go-loose/Chart.yaml +++ b/deploy/helm/go-loose/Chart.yaml @@ -2,9 +2,14 @@ apiVersion: v2 name: go-loose description: Authentication and authorization service for urpi platform. Provides OIDC, session management, and tenant isolation. type: application -version: 1.0.0 +version: 1.1.0 appVersion: "1.0.0" kubeVersion: ">=1.28.0-0" +dependencies: + - name: postgresql + version: "16.x.x" + repository: "https://charts.bitnami.com/bitnami" + condition: postgresql.enabled keywords: - authentication - authorization @@ -20,4 +25,4 @@ maintainers: url: https://github.com/urpi annotations: category: Security - licenses: MIT \ No newline at end of file + licenses: MIT diff --git a/deploy/helm/go-loose/README.md b/deploy/helm/go-loose/README.md new file mode 100644 index 0000000..cf1b2c6 --- /dev/null +++ b/deploy/helm/go-loose/README.md @@ -0,0 +1,81 @@ +# Go Loose Helm chart + +This is the canonical Go Loose chart. The central deploy script and the Go Loose +CI workflow both use this directory. + +## Environment profiles + +Use one profile file per environment: + +| Environment | Namespace | Profile | Ingress class | TLS issuer | +|---|---|---|---|---| +| `dev` | `go-insane-dev` | `values-dev.yaml` | nginx | `letsencrypt-staging` | +| `tst` | `go-insane-tst` | `values-tst.yaml` | nginx | `letsencrypt-staging` | +| `prd` | `go-insane-prd` | `values-prd.yaml` | nginx | `letsencrypt-prod` | +| `lcl` | `go-insane-lcl` | `values-lcl.yaml` | Traefik | local TLS | + +The namespace is supplied by Helm's release namespace; it is not hard-coded in +the chart templates. + +## Runtime Secret contract + +Before a release, the target namespace must contain `go-loose-runtime` with: + +| Key | Used for | +|---|---| +| `postgresql-password` | PostgreSQL application user's password | +| `postgres-password` | PostgreSQL administrator password | +| `database-url` | `GO_LOOSE_DATABASE_URL` | +| `session-secret` | `GO_LOOSE_SESSION_SECRET` | + +The chart reads these values using Kubernetes `secretKeyRef` and the PostgreSQL +subchart's `existingSecret` support. Runtime credentials are not stored in chart +values or Helm release configuration. + +To provision/update the secret from protected files without printing its values: + +```bash +/home/dnoulet/go/ops/scripts/apply-go-loose-secret.sh \ + \ + /secure/path/postgresql-password \ + /secure/path/database-url \ + /secure/path/session-secret \ + [kubeconfig] +``` + +The input files should contain only the required value (no trailing newline). +The helper creates the environment namespace if it is not present, then applies +the Secret. It does not print secret values. + +The PRD CI deployment script maintains the same secret contract automatically. +Do not commit the input files or put their contents in a Helm values file. + +## Tenant wildcard ingress + +Go Guess signs in through tenant-specific hosts such as +`nmbs.auth.dev-urpi.be` and `nmbs.auth.urpi.be`. The chart therefore +creates a second ingress for `*.auth-...` and requires a separate TLS Secret +named `go-loose-wildcard-tls` in the same namespace. The central deploy script +and PRD CI check for this Secret before installing the release. + +Provision it from a trusted wildcard certificate and private key: + +```bash +kubectl --kubeconfig --namespace go-insane- create secret tls \ + go-loose-wildcard-tls --cert=/secure/path/wildcard.crt \ + --key=/secure/path/wildcard.key +``` + +The PRD `letsencrypt-prod` issuer documented for the apex ingress uses HTTP-01; +HTTP-01 cannot issue wildcard certificates. Obtain this certificate through +DNS-01 or the approved certificate authority, then provision the Secret. Do not +put the private key in the chart, repository, or Helm release values. + +## Validation + +```bash +helm lint . -f values-dev.yaml +helm lint . -f values-tst.yaml +helm lint . -f values-prd.yaml +helm lint . -f values-lcl.yaml +``` diff --git a/deploy/helm/go-loose/templates/deployment.yaml b/deploy/helm/go-loose/templates/deployment.yaml index cf51f60..bd9fb28 100644 --- a/deploy/helm/go-loose/templates/deployment.yaml +++ b/deploy/helm/go-loose/templates/deployment.yaml @@ -40,14 +40,20 @@ spec: containerPort: {{ .Values.service.targetPort }} protocol: TCP env: + - name: GO_LOOSE_DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ .Values.runtimeSecret.name }} + key: {{ .Values.runtimeSecret.keys.databaseURL }} + - name: GO_LOOSE_SESSION_SECRET + valueFrom: + secretKeyRef: + name: {{ .Values.runtimeSecret.name }} + key: {{ .Values.runtimeSecret.keys.sessionSecret }} {{- range $key, $value := .Values.env }} - name: {{ $key }} value: {{ $value | quote }} {{- end }} - {{- range $key, $value := .Values.envSecrets }} - - name: {{ $key }} - value: {{ $value | quote }} - {{- end }} livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: @@ -65,4 +71,4 @@ spec: {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} - {{- end }} \ No newline at end of file + {{- end }} diff --git a/deploy/helm/go-loose/templates/secret.yaml b/deploy/helm/go-loose/templates/secret.yaml deleted file mode 100644 index 5c0fab9..0000000 --- a/deploy/helm/go-loose/templates/secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "go-loose.fullname" . }}-env - namespace: {{ .Release.Namespace }} - labels: - {{- include "go-loose.labels" . | nindent 4 }} -type: Opaque -stringData: - {{- range $key, $value := .Values.envSecrets }} - {{ $key }}: {{ $value | quote }} - {{- end }} \ No newline at end of file diff --git a/deploy/helm/go-loose/templates/wildcard-ingress.yaml b/deploy/helm/go-loose/templates/wildcard-ingress.yaml new file mode 100644 index 0000000..b7fbfca --- /dev/null +++ b/deploy/helm/go-loose/templates/wildcard-ingress.yaml @@ -0,0 +1,30 @@ +{{- if .Values.wildcardIngress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "go-loose.fullname" . }}-wildcard + namespace: {{ .Release.Namespace }} + labels: + {{- include "go-loose.labels" . | nindent 4 }} + {{- with .Values.wildcardIngress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ingressClassName: {{ .Values.wildcardIngress.className }} + tls: + - secretName: {{ .Values.wildcardIngress.tlsSecretName }} + hosts: + - {{ .Values.wildcardIngress.host | quote }} + rules: + - host: {{ .Values.wildcardIngress.host | quote }} + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: {{ include "go-loose.fullname" . }} + port: + number: {{ .Values.service.port }} +{{- end }} diff --git a/deploy/helm/go-loose/values-dev.yaml b/deploy/helm/go-loose/values-dev.yaml new file mode 100644 index 0000000..185b2f1 --- /dev/null +++ b/deploy/helm/go-loose/values-dev.yaml @@ -0,0 +1,46 @@ +replicaCount: 1 + +image: + repository: nexus.urpi.be/docker-hosted/go-loose + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: nexus-registry + +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-staging + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: auth.dev-urpi.be + paths: + - path: / + pathType: Prefix + tls: + - secretName: go-loose-dev-tls + hosts: + - auth.dev-urpi.be + +wildcardIngress: + enabled: true + className: nginx + host: "*.auth.dev-urpi.be" + tlsSecretName: go-loose-wildcard-tls + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + +postgresql: + primary: + persistence: + enabled: true + size: 5Gi + +env: + GO_LOOSE_BASE_URL: https://auth.dev-urpi.be + GO_LOOSE_AUTH_DOMAIN: auth.dev-urpi.be + GO_LOOSE_OIDC_REDIRECT_URL: https://auth.dev-urpi.be/auth/callback + GO_LOOSE_DEV_LOGIN: "true" + GO_LOOSE_LOCAL_LOGIN: "true" diff --git a/deploy/helm/go-loose/values-lcl.yaml b/deploy/helm/go-loose/values-lcl.yaml new file mode 100644 index 0000000..d17bfcc --- /dev/null +++ b/deploy/helm/go-loose/values-lcl.yaml @@ -0,0 +1,65 @@ +replicaCount: 1 +imagePullSecrets: [] + +ingress: + enabled: true + className: traefik + annotations: + cert-manager.io/cluster-issuer: urpi-private-ca + hosts: + # Apex only. The tenant hosts are served by wildcardIngress below, so that + # each host family keeps its own certificate: go-loose-lcl-tls covers the + # apex, go-loose-wildcard-tls covers *.auth.lcl-urpi.be. + - host: auth.lcl-urpi.be + paths: + - path: / + pathType: Prefix + tls: + - secretName: go-loose-lcl-tls + hosts: + - auth.lcl-urpi.be + +# Serves every .auth.lcl-urpi.be. Adding a tenant is then a row in the +# database only: no Helm upgrade, no new certificate, no per-host rule. +wildcardIngress: + enabled: true + className: traefik + host: "*.auth.lcl-urpi.be" + tlsSecretName: go-loose-wildcard-tls + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + +certManager: + enabled: true + issuerRef: + name: urpi-private-ca + kind: ClusterIssuer + +postgresql: + enabled: true + image: + # Bitnami removed its versioned tags from Docker Hub; only :latest remains + # for bitnami/postgresql. The tag pinned in values.yaml (16.4.0) now 404s + # and leaves the StatefulSet in ImagePullBackOff. + tag: "latest" + auth: + database: goloose + username: goloose + password: "goloose" + existingSecret: go-loose-runtime + secretKeys: + adminPasswordKey: postgres-password + userPasswordKey: postgresql-password + primary: + persistence: + enabled: false + size: 5Gi + +env: + GO_LOOSE_ADDR: ":8080" + GO_LOOSE_BASE_URL: "https://lcl-urpi.be" + GO_LOOSE_AUTH_DOMAIN: "auth.lcl-urpi.be" + GO_LOOSE_OIDC_REDIRECT_URL: "https://lcl-urpi.be/auth/callback" + GO_LOOSE_DEV_LOGIN: "true" + GO_LOOSE_LOCAL_LOGIN: "true" + GO_LOOSE_ALLOW_PRIVATE_CONTRACT_URLS: "true" diff --git a/deploy/helm/go-loose/values-prd.yaml b/deploy/helm/go-loose/values-prd.yaml index 4ed5fda..93f4dcb 100644 --- a/deploy/helm/go-loose/values-prd.yaml +++ b/deploy/helm/go-loose/values-prd.yaml @@ -5,7 +5,7 @@ imagePullSecrets: - name: nexus-registry image: - repository: nexus-registry.urpi.be/docker-hosted/go-loose + repository: nexus.urpi.be/docker-hosted/go-loose tag: "0.0.1" pullPolicy: Always @@ -16,9 +16,10 @@ service: ingress: enabled: true - className: traefik + className: nginx annotations: cert-manager.io/cluster-issuer: letsencrypt-prod + nginx.ingress.kubernetes.io/ssl-redirect: "true" # nginx.ingress.kubernetes.io/ssl-redirect: "true" # nginx.ingress.kubernetes.io/proxy-body-size: "10m" hosts: @@ -31,6 +32,14 @@ ingress: hosts: - auth.urpi.be +wildcardIngress: + enabled: true + className: nginx + host: "*.auth.urpi.be" + tlsSecretName: go-loose-wildcard-tls + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + certManager: enabled: true issuerRef: @@ -69,13 +78,9 @@ env: GO_LOOSE_AUTH_DOMAIN: "auth.urpi.be" GO_LOOSE_OIDC_REDIRECT_URL: "https://auth.urpi.be/auth/callback" GO_LOOSE_DEV_LOGIN: "false" - GO_LOOSE_LOCAL_LOGIN: "true" + GO_LOOSE_LOCAL_LOGIN: "false" GO_LOOSE_ALLOW_PRIVATE_CONTRACT_URLS: "true" -envSecrets: - GO_LOOSE_DATABASE_URL: "postgres://goloose:changeme@go-loose-postgresql:5432/goloose?sslmode=disable" - GO_LOOSE_SESSION_SECRET: "changeme-generate-secure-random" - livenessProbe: httpGet: path: /healthz @@ -92,4 +97,4 @@ readinessProbe: initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 3 - failureThreshold: 3 \ No newline at end of file + failureThreshold: 3 diff --git a/deploy/helm/go-loose/values-tst.yaml b/deploy/helm/go-loose/values-tst.yaml new file mode 100644 index 0000000..d656f2e --- /dev/null +++ b/deploy/helm/go-loose/values-tst.yaml @@ -0,0 +1,46 @@ +replicaCount: 1 + +image: + repository: nexus.urpi.be/docker-hosted/go-loose + tag: latest + pullPolicy: Always + +imagePullSecrets: + - name: nexus-registry + +ingress: + enabled: true + className: nginx + annotations: + cert-manager.io/cluster-issuer: letsencrypt-staging + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: auth.tst-urpi.be + paths: + - path: / + pathType: Prefix + tls: + - secretName: go-loose-tst-tls + hosts: + - auth.tst-urpi.be + +wildcardIngress: + enabled: true + className: nginx + host: "*.auth.tst-urpi.be" + tlsSecretName: go-loose-wildcard-tls + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "true" + +postgresql: + primary: + persistence: + enabled: true + size: 5Gi + +env: + GO_LOOSE_BASE_URL: https://auth.tst-urpi.be + GO_LOOSE_AUTH_DOMAIN: auth.tst-urpi.be + GO_LOOSE_OIDC_REDIRECT_URL: https://auth.tst-urpi.be/auth/callback + GO_LOOSE_DEV_LOGIN: "false" + GO_LOOSE_LOCAL_LOGIN: "false" diff --git a/deploy/helm/go-loose/values.yaml b/deploy/helm/go-loose/values.yaml index 5e49e67..2e2b0c1 100644 --- a/deploy/helm/go-loose/values.yaml +++ b/deploy/helm/go-loose/values.yaml @@ -54,6 +54,13 @@ ingress: hosts: - auth.urpi.be +wildcardIngress: + enabled: false + className: nginx + host: "*.auth.urpi.be" + tlsSecretName: go-loose-wildcard-tls + annotations: {} + certManager: enabled: true issuerRef: @@ -74,12 +81,24 @@ autoscaling: maxReplicas: 3 targetCPUUtilizationPercentage: 80 +runtimeSecret: + name: go-loose-runtime + keys: + databaseURL: database-url + sessionSecret: session-secret + postgresql: enabled: true + image: + tag: "16.4.0" auth: database: goloose username: goloose - password: changeme + password: "goloose" + existingSecret: go-loose-runtime + secretKeys: + adminPasswordKey: postgres-password + userPasswordKey: postgresql-password primary: persistence: enabled: true @@ -98,13 +117,9 @@ env: GO_LOOSE_AUTH_DOMAIN: "auth.urpi.be" GO_LOOSE_OIDC_REDIRECT_URL: "https://auth.urpi.be/auth/callback" GO_LOOSE_DEV_LOGIN: "false" - GO_LOOSE_LOCAL_LOGIN: "true" + GO_LOOSE_LOCAL_LOGIN: "false" GO_LOOSE_ALLOW_PRIVATE_CONTRACT_URLS: "true" -envSecrets: - GO_LOOSE_DATABASE_URL: "" - GO_LOOSE_SESSION_SECRET: "" - livenessProbe: httpGet: path: /healthz @@ -125,4 +140,4 @@ readinessProbe: nodeSelector: {} tolerations: [] -affinity: {} \ No newline at end of file +affinity: {} diff --git a/docs/commands b/docs/commands new file mode 100644 index 0000000..da94146 --- /dev/null +++ b/docs/commands @@ -0,0 +1,18 @@ +# Delete the incomplete secret +kubectl delete secret go-loose-runtime --kubeconfig=/home/dnoulet/.kube/config_lcl --insecure-skip-tls-verify -n go-insane-lcl --ignore-not-found + +# Recreate it with all required properties mapped correctly +kubectl create secret generic go-loose-runtime \ + --from-literal=postgres-password="goloose" \ + --from-literal=postgresql-password="goloose" \ + --from-literal=session-secret="localdevsecret123" \ + --from-literal=database-url="postgres://goloose:goloose@go-loose-postgresql:5432/goloose?sslmode=disable" \ + --kubeconfig=/home/dnoulet/.kube/config_lcl \ + --insecure-skip-tls-verify \ + -n go-insane-lcl + +KUBECONFIG=~/.kube/config_lcl kubectl config set-cluster local --insecure-skip-tls-verify=true + +openssl s_client -showcerts -connect rancher.intern-urpi.be:443 /dev/null | sed -n '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ~/.kube/rancher-ca.crt +KUBECONFIG=~/.kube/config_lcl kubectl config set-cluster local --certificate-authority=/home/dnoulet/.kube/rancher-ca.crt +KUBECONFIG=~/.kube/config_lcl kubectl config set-cluster local --insecure-skip-tls-verify=false