fix(k8s): stabilize keycloak rollout and align CD deploy manifests

Update Keycloak probe/realm import behavior and authority config so auth services start reliably on the dev cluster, while keeping CD deployment steps aligned with the actual Kubernetes overlay behavior.
This commit is contained in:
WorkClub Automation
2026-03-13 06:25:07 +01:00
parent 7272358746
commit eaa163afa4
9 changed files with 394 additions and 46 deletions

View File

@@ -47,11 +47,12 @@ jobs:
- name: Kustomize Edit Image Tag - name: Kustomize Edit Image Tag
working-directory: ./infra/k8s/overlays/dev working-directory: ./infra/k8s/overlays/dev
run: | run: |
kustomize edit set image 192.168.241.13:8080/workclub-api=192.168.241.13:8080/workclub-api:$IMAGE_TAG kustomize edit set image workclub-api=192.168.241.13:8080/workclub-api:$IMAGE_TAG
kustomize edit set image 192.168.241.13:8080/workclub-frontend=192.168.241.13:8080/workclub-frontend:$IMAGE_TAG kustomize edit set image workclub-frontend=192.168.241.13:8080/workclub-frontend:$IMAGE_TAG
- name: Deploy to Kubernetes - name: Deploy to Kubernetes
run: | run: |
set -euo pipefail
export KUBECONFIG=$HOME/.kube/config export KUBECONFIG=$HOME/.kube/config
mkdir -p $HOME/.kube mkdir -p $HOME/.kube
if echo "${{ secrets.KUBECONFIG }}" | grep -q "apiVersion"; then if echo "${{ secrets.KUBECONFIG }}" | grep -q "apiVersion"; then
@@ -63,28 +64,34 @@ jobs:
printf '%s' "${{ secrets.KUBECONFIG }}" | base64 -d > $KUBECONFIG printf '%s' "${{ secrets.KUBECONFIG }}" | base64 -d > $KUBECONFIG
fi fi
chmod 600 $KUBECONFIG chmod 600 $KUBECONFIG
kubectl --kubeconfig="$KUBECONFIG" config view >/dev/null
# Diagnostics # Diagnostics
echo "Kubeconfig path: $KUBECONFIG" echo "Kubeconfig path: $KUBECONFIG"
echo "Kubeconfig size: $(wc -c < $KUBECONFIG) bytes" echo "Kubeconfig size: $(wc -c < $KUBECONFIG) bytes"
echo "Available contexts:" echo "Available contexts:"
kubectl config get-contexts kubectl --kubeconfig="$KUBECONFIG" config get-contexts
if ! grep -q "current-context" $KUBECONFIG; then if ! grep -q "current-context" $KUBECONFIG; then
echo "Warning: current-context missing, attempting to fix..." echo "Warning: current-context missing, attempting to fix..."
FIRST_CONTEXT=$(kubectl config get-contexts -o name | head -n 1) FIRST_CONTEXT=$(kubectl --kubeconfig="$KUBECONFIG" config get-contexts -o name | head -n 1)
if [ -n "$FIRST_CONTEXT" ]; then if [ -n "$FIRST_CONTEXT" ]; then
kubectl config use-context "$FIRST_CONTEXT" kubectl --kubeconfig="$KUBECONFIG" config use-context "$FIRST_CONTEXT"
fi fi
fi fi
echo "Current context: $(kubectl config current-context)" echo "Current context: $(kubectl --kubeconfig="$KUBECONFIG" config current-context)"
# Ensure target namespace exists # Ensure target namespace exists
kubectl create namespace workclub-dev --dry-run=client -o yaml | kubectl apply -f - kubectl --kubeconfig="$KUBECONFIG" create namespace workclub-dev --dry-run=client -o yaml | kubectl --kubeconfig="$KUBECONFIG" apply -f -
# Delete existing StatefulSet to allow immutable field changes (vct -> emptyDir) # Apply manifests (non-destructive by default; avoid DB state churn)
kubectl delete statefulset workclub-postgres -n workclub-dev --ignore-not-found kubectl --kubeconfig="$KUBECONFIG" config view --minify # Verification of context
kustomize build --load-restrictor LoadRestrictionsNone infra/k8s/overlays/dev | kubectl --kubeconfig="$KUBECONFIG" apply -f -
kubectl config view --minify # Verification of context
kubectl apply -k infra/k8s/overlays/dev # Rollout verification
kubectl --kubeconfig="$KUBECONFIG" rollout status statefulset/workclub-postgres -n workclub-dev --timeout=300s
kubectl --kubeconfig="$KUBECONFIG" rollout status deployment/workclub-keycloak -n workclub-dev --timeout=600s
kubectl --kubeconfig="$KUBECONFIG" rollout status deployment/workclub-api -n workclub-dev --timeout=300s
kubectl --kubeconfig="$KUBECONFIG" rollout status deployment/workclub-frontend -n workclub-dev --timeout=300s

View File

@@ -12,6 +12,7 @@
> - Docker Compose for local development (hot reload, Keycloak, PostgreSQL) > - Docker Compose for local development (hot reload, Keycloak, PostgreSQL)
> - Kubernetes manifests (Kustomize base + dev overlay) > - Kubernetes manifests (Kustomize base + dev overlay)
> - Gitea CI pipeline (`.gitea/workflows/ci.yml`) for backend/frontend/infrastructure validation > - Gitea CI pipeline (`.gitea/workflows/ci.yml`) for backend/frontend/infrastructure validation
> - Gitea CD bootstrap + deployment pipelines (`.gitea/workflows/cd-bootstrap.yml`, `.gitea/workflows/cd-deploy.yml`)
> - Comprehensive TDD test suite (xUnit + Testcontainers, Vitest + RTL, Playwright E2E) > - Comprehensive TDD test suite (xUnit + Testcontainers, Vitest + RTL, Playwright E2E)
> - Seed data for development (2 clubs, 5 users, sample tasks + shifts) > - Seed data for development (2 clubs, 5 users, sample tasks + shifts)
> >
@@ -36,7 +37,7 @@ Build a multi-tenant internet application for managing work items over several m
- **Testing**: TDD approach (tests first). - **Testing**: TDD approach (tests first).
- **Notifications**: None for MVP. - **Notifications**: None for MVP.
- **CI extension**: Add Gitea-hosted CI pipeline for this repository. - **CI extension**: Add Gitea-hosted CI pipeline for this repository.
- **Pipeline scope**: CI-only (build/test/lint/manifest validation), no auto-deploy in this iteration. - **Pipeline scope (updated)**: CI + CD. CI handles build/test/lint/manifest validation; CD bootstrap publishes multi-arch images; CD deploy applies Kubernetes manifests.
**Research Findings**: **Research Findings**:
- **Finbuckle.MultiTenant**: ClaimStrategy + HeaderStrategy fallback is production-proven (fullstackhero/dotnet-starter-kit pattern). - **Finbuckle.MultiTenant**: ClaimStrategy + HeaderStrategy fallback is production-proven (fullstackhero/dotnet-starter-kit pattern).
@@ -73,6 +74,8 @@ Deliver a working multi-tenant club work management application where authentica
- `/docker-compose.yml` — Local dev stack (PostgreSQL, Keycloak, .NET API, Next.js) - `/docker-compose.yml` — Local dev stack (PostgreSQL, Keycloak, .NET API, Next.js)
- `/infra/k8s/` — Kustomize manifests (base + dev overlay) - `/infra/k8s/` — Kustomize manifests (base + dev overlay)
- `/.gitea/workflows/ci.yml` — Gitea Actions CI pipeline (parallel backend/frontend/infra checks) - `/.gitea/workflows/ci.yml` — Gitea Actions CI pipeline (parallel backend/frontend/infra checks)
- `/.gitea/workflows/cd-bootstrap.yml` — Gitea Actions CD bootstrap workflow (manual multi-arch image publish)
- `/.gitea/workflows/cd-deploy.yml` — Gitea Actions CD deployment workflow (Kubernetes deploy with Kustomize overlay)
- PostgreSQL database with RLS policies on all tenant-scoped tables - PostgreSQL database with RLS policies on all tenant-scoped tables
- Keycloak realm configuration with test users and club memberships - Keycloak realm configuration with test users and club memberships
- Seed data for development - Seed data for development
@@ -106,6 +109,8 @@ Deliver a working multi-tenant club work management application where authentica
- TDD: all backend features have tests BEFORE implementation - TDD: all backend features have tests BEFORE implementation
- Gitea-hosted CI pipeline for this repository (`code.hal9000.damnserver.com/MasterMito/work-club-manager`) - Gitea-hosted CI pipeline for this repository (`code.hal9000.damnserver.com/MasterMito/work-club-manager`)
- CI jobs run in parallel (backend, frontend, infrastructure validation) - CI jobs run in parallel (backend, frontend, infrastructure validation)
- Gitea-hosted CD bootstrap workflow for private registry image publication (`workclub-api`, `workclub-frontend`)
- Gitea-hosted CD deployment workflow for Kubernetes dev namespace rollout (`workclub-dev`)
### Must NOT Have (Guardrails) ### Must NOT Have (Guardrails)
- **No CQRS/MediatR** — Direct service injection from controllers/endpoints - **No CQRS/MediatR** — Direct service injection from controllers/endpoints
@@ -124,7 +129,7 @@ Deliver a working multi-tenant club work management application where authentica
- **No in-memory database for tests** — Real PostgreSQL via Testcontainers - **No in-memory database for tests** — Real PostgreSQL via Testcontainers
- **No billing, subscriptions, or analytics dashboard** - **No billing, subscriptions, or analytics dashboard**
- **No mobile app** - **No mobile app**
- **No automatic deployment in this CI extension** — CD remains out-of-scope for this append - **No single-step build-and-deploy coupling** — keep image bootstrap and cluster deployment as separate workflows
--- ---
@@ -193,11 +198,11 @@ Wave 5 (After Wave 4 — polish + Docker):
├── Task 24: Frontend Dockerfiles (dev + prod standalone) (depends: 18) [quick] ├── Task 24: Frontend Dockerfiles (dev + prod standalone) (depends: 18) [quick]
└── Task 25: Kustomize dev overlay + resource limits + health checks (depends: 6, 23, 24) [unspecified-high] └── Task 25: Kustomize dev overlay + resource limits + health checks (depends: 6, 23, 24) [unspecified-high]
Wave 6 (After Wave 5 — E2E + integration): Wave 6 (After Wave 5 — E2E + CI/CD integration):
├── Task 26: Playwright E2E tests — auth flow + club switching (depends: 21, 22) [unspecified-high] ├── Task 26: Playwright E2E tests — auth flow + club switching (depends: 21, 22) [unspecified-high]
├── Task 27: Playwright E2E tests — task management flow (depends: 19, 22) [unspecified-high] ├── Task 27: Playwright E2E tests — task management flow (depends: 19, 22) [unspecified-high]
├── Task 28: Playwright E2E tests — shift sign-up flow (depends: 20, 22) [unspecified-high] ├── Task 28: Playwright E2E tests — shift sign-up flow (depends: 20, 22) [unspecified-high]
└── Task 29: Gitea CI workflow (backend + frontend + infra checks) (depends: 12, 17, 23, 24, 25) [unspecified-high] └── Task 29: Gitea CI/CD workflows (CI checks + image bootstrap + Kubernetes deploy) (depends: 12, 17, 23, 24, 25) [unspecified-high]
Wave FINAL (After ALL tasks — independent review, 4 parallel): Wave FINAL (After ALL tasks — independent review, 4 parallel):
├── Task F1: Plan compliance audit (oracle) ├── Task F1: Plan compliance audit (oracle)
@@ -2525,34 +2530,37 @@ Max Concurrent: 6 (Wave 1)
- Files: `frontend/tests/e2e/shifts.spec.ts` - Files: `frontend/tests/e2e/shifts.spec.ts`
- Pre-commit: `bunx playwright test tests/e2e/shifts.spec.ts` - Pre-commit: `bunx playwright test tests/e2e/shifts.spec.ts`
- [x] 29. Gitea CI Pipeline — Backend + Frontend + Infra Validation - [x] 29. Gitea CI/CD PipelinesCI Validation + Image Bootstrap + Kubernetes Deploy
**What to do**: **What to do**:
- Create `.gitea/workflows/ci.yml` for repository `code.hal9000.damnserver.com/MasterMito/work-club-manager` - Maintain `.gitea/workflows/ci.yml` for repository `code.hal9000.damnserver.com/MasterMito/work-club-manager`
- Configure triggers: - Maintain `.gitea/workflows/cd-bootstrap.yml` for manual multi-arch image publishing to private registry
- Maintain `.gitea/workflows/cd-deploy.yml` for Kubernetes deployment using Kustomize overlays
- Configure CI triggers:
- `push` on `main` and feature branches - `push` on `main` and feature branches
- `pull_request` targeting `main` - `pull_request` targeting `main`
- `workflow_dispatch` for manual reruns - `workflow_dispatch` for manual reruns
- Structure pipeline into parallel jobs (fail-fast disabled so all diagnostics are visible): - CI workflow structure (parallel validation jobs):
- `backend-ci`: setup .NET 10 SDK, restore, build, run backend unit/integration tests - `backend-ci`: setup .NET 10 SDK, restore, build, run backend unit/integration tests
- `frontend-ci`: setup Bun, install deps, run lint, type-check, unit tests, production build - `frontend-ci`: setup Bun, install deps, run lint, type-check, unit tests, production build
- `infra-ci`: validate Docker Compose and Kustomize manifests - `infra-ci`: validate Docker Compose and Kustomize manifests
- Add path filters so docs-only changes skip heavy jobs when possible - CD bootstrap workflow behavior:
- Add dependency caching: - Manual trigger with `image_tag` + build flags
- NuGet cache keyed by `**/*.csproj` + lock/context - Buildx multi-arch build (`linux/amd64,linux/arm64`) for `workclub-api` and `workclub-frontend`
- Bun cache keyed by `bun.lockb` - Push image tags to `192.168.241.13:8080` and emit task-31/task-32/task-33 evidence artifacts
- Add artifact upload on failure: - CD deploy workflow behavior:
- `backend-test-results` (trx/log output) - Triggered by successful bootstrap (`workflow_run`) or manual dispatch (`image_tag` input)
- `frontend-test-results` (vitest output) - Install kubectl + kustomize on runner
- `infra-validation-output` - Run `kustomize edit set image` in `infra/k8s/overlays/dev`
- Apply manifests with `kubectl apply -k infra/k8s/overlays/dev`
- Ensure namespace `workclub-dev` exists and perform deployment diagnostics
- Enforce branch protection expectation in plan notes: - Enforce branch protection expectation in plan notes:
- Required checks: `backend-ci`, `frontend-ci`, `infra-ci` - Required checks: `backend-ci`, `frontend-ci`, `infra-ci`
- Keep CD out-of-scope in this append (no image push, no deploy steps)
**Must NOT do**: **Must NOT do**:
- Do NOT add deployment jobs (Kubernetes apply/helm/kustomize deploy) - Do NOT collapse bootstrap and deployment into one opaque pipeline stage
- Do NOT add secrets for registry push in this CI-only iteration - Do NOT bypass image-tag pinning in deployment
- Do NOT couple CI workflow to release-tag deployment behavior - Do NOT remove CI validation gates (`backend-ci`, `frontend-ci`, `infra-ci`)
**Recommended Agent Profile**: **Recommended Agent Profile**:
- **Category**: `unspecified-high` - **Category**: `unspecified-high`
@@ -2568,10 +2576,13 @@ Max Concurrent: 6 (Wave 1)
**References**: **References**:
**Pattern References**: **Pattern References**:
- `.gitea/workflows/ci.yml` — Source of truth for CI checks
- `.gitea/workflows/cd-bootstrap.yml` — Source of truth for image publish bootstrap
- `.gitea/workflows/cd-deploy.yml` — Source of truth for deployment apply logic
- `docker-compose.yml` — Source of truth for `docker compose config` validation - `docker-compose.yml` — Source of truth for `docker compose config` validation
- `infra/k8s/base/kustomization.yaml` and `infra/k8s/overlays/dev/kustomization.yaml` — Kustomize build inputs used by infra-ci job - `infra/k8s/base/kustomization.yaml` and `infra/k8s/overlays/dev/kustomization.yaml` — Kustomize build/apply inputs
- `backend/WorkClub.sln` — Backend restore/build/test entrypoint for .NET job - `backend/WorkClub.sln` — Backend restore/build/test entrypoint for .NET job
- `frontend/package.json` + `frontend/bun.lockb` — Frontend scripts and cache key anchor - `frontend/package.json` + `frontend/bun.lock` — Frontend scripts and cache key anchor
**External References**: **External References**:
- Gitea Actions docs: workflow syntax and trigger model (`.gitea/workflows/*.yml`) - Gitea Actions docs: workflow syntax and trigger model (`.gitea/workflows/*.yml`)
@@ -2596,6 +2607,18 @@ Max Concurrent: 6 (Wave 1)
Failure Indicators: Missing job, skipped required job, or non-success conclusion Failure Indicators: Missing job, skipped required job, or non-success conclusion
Evidence: .sisyphus/evidence/task-29-gitea-ci-success.json Evidence: .sisyphus/evidence/task-29-gitea-ci-success.json
Scenario: CD bootstrap and deploy workflows are present and wired
Tool: Bash
Preconditions: Repository contains workflow files
Steps:
1. Assert `.gitea/workflows/cd-bootstrap.yml` exists
2. Assert `.gitea/workflows/cd-deploy.yml` exists
3. Grep bootstrap workflow for buildx multi-arch publish step
4. Grep deploy workflow for `workflow_run`, `kustomize edit set image`, and `kubectl apply -k`
Expected Result: Both CD workflows exist with expected bootstrap and deploy steps
Failure Indicators: Missing file, missing trigger, or missing deploy commands
Evidence: .sisyphus/evidence/task-29-gitea-cd-workflows.txt
Scenario: Pipeline fails on intentional backend break Scenario: Pipeline fails on intentional backend break
Tool: Bash (git + Gitea API) Tool: Bash (git + Gitea API)
Preconditions: Temporary branch available, ability to push test commit Preconditions: Temporary branch available, ability to push test commit
@@ -2611,8 +2634,8 @@ Max Concurrent: 6 (Wave 1)
``` ```
**Commit**: YES **Commit**: YES
- Message: `ci(gitea): add parallel CI workflow for backend, frontend, and infra validation` - Message: `ci(cd): add CI validation plus bootstrap and Kubernetes deployment workflows`
- Files: `.gitea/workflows/ci.yml` - Files: `.gitea/workflows/ci.yml`, `.gitea/workflows/cd-bootstrap.yml`, `.gitea/workflows/cd-deploy.yml`
- Pre-commit: `docker compose config && kustomize build infra/k8s/overlays/dev > /dev/null` - Pre-commit: `docker compose config && kustomize build infra/k8s/overlays/dev > /dev/null`
--- ---
@@ -2662,7 +2685,7 @@ Max Concurrent: 6 (Wave 1)
| 4 | T18-T21 | `feat(ui): add layout, club-switcher, login, task and shift pages` | frontend/src/app/**/*.tsx, frontend/src/components/**/*.tsx | `bun run build && bun run test` | | 4 | T18-T21 | `feat(ui): add layout, club-switcher, login, task and shift pages` | frontend/src/app/**/*.tsx, frontend/src/components/**/*.tsx | `bun run build && bun run test` |
| 5 | T22-T25 | `infra(deploy): add full Docker Compose stack, Dockerfiles, and Kustomize dev overlay` | docker-compose.yml, **/Dockerfile*, infra/k8s/overlays/dev/**/*.yaml | `docker compose config && kustomize build infra/k8s/overlays/dev` | | 5 | T22-T25 | `infra(deploy): add full Docker Compose stack, Dockerfiles, and Kustomize dev overlay` | docker-compose.yml, **/Dockerfile*, infra/k8s/overlays/dev/**/*.yaml | `docker compose config && kustomize build infra/k8s/overlays/dev` |
| 6 | T26-T28 | `test(e2e): add Playwright E2E tests for auth, tasks, and shifts` | frontend/tests/e2e/**/*.spec.ts | `bunx playwright test` | | 6 | T26-T28 | `test(e2e): add Playwright E2E tests for auth, tasks, and shifts` | frontend/tests/e2e/**/*.spec.ts | `bunx playwright test` |
| 6 | T29 | `ci(gitea): add parallel CI workflow for backend, frontend, and infra validation` | .gitea/workflows/ci.yml | `docker compose config && kustomize build infra/k8s/overlays/dev > /dev/null` | | 6 | T29 | `ci(cd): add CI validation plus bootstrap and Kubernetes deployment workflows` | .gitea/workflows/ci.yml, .gitea/workflows/cd-bootstrap.yml, .gitea/workflows/cd-deploy.yml | `docker compose config && kustomize build infra/k8s/overlays/dev > /dev/null` |
--- ---
@@ -2699,6 +2722,12 @@ kustomize build infra/k8s/overlays/dev > /dev/null # Expected: Exit 0
# CI workflow file present and includes required jobs # CI workflow file present and includes required jobs
grep -E "backend-ci|frontend-ci|infra-ci" .gitea/workflows/ci.yml # Expected: all 3 job names present grep -E "backend-ci|frontend-ci|infra-ci" .gitea/workflows/ci.yml # Expected: all 3 job names present
# CD bootstrap workflow present with multi-arch publish
grep -E "buildx|linux/amd64,linux/arm64|workclub-api|workclub-frontend" .gitea/workflows/cd-bootstrap.yml
# CD deploy workflow present with deploy trigger and apply step
grep -E "workflow_run|kustomize edit set image|kubectl apply -k" .gitea/workflows/cd-deploy.yml
``` ```
### Final Checklist ### Final Checklist
@@ -2710,6 +2739,7 @@ grep -E "backend-ci|frontend-ci|infra-ci" .gitea/workflows/ci.yml # Expected: a
- [x] Docker Compose stack starts clean and healthy - [x] Docker Compose stack starts clean and healthy
- [x] Kustomize manifests build without errors - [x] Kustomize manifests build without errors
- [x] Gitea CI workflow exists and references backend-ci/frontend-ci/infra-ci - [x] Gitea CI workflow exists and references backend-ci/frontend-ci/infra-ci
- [x] Gitea CD bootstrap and deploy workflows exist and are wired to image publish/deploy steps
- [x] RLS isolation proven at database level - [x] RLS isolation proven at database level
- [x] Cross-tenant access returns 403 - [x] Cross-tenant access returns 403
- [x] Task state machine rejects invalid transitions (422) - [x] Task state machine rejects invalid transitions (422)

View File

@@ -62,6 +62,22 @@ public class SeedDataService
"); ");
// Create admin bypass policies (idempotent) // Create admin bypass policies (idempotent)
await context.Database.ExecuteSqlRawAsync(@"
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_admin') THEN
CREATE ROLE app_admin;
END IF;
END
$$;
GRANT app_admin TO app;
GRANT USAGE ON SCHEMA public TO app_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO app_admin;
ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON TABLES TO app_admin;
ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON SEQUENCES TO app_admin;
");
await context.Database.ExecuteSqlRawAsync(@" await context.Database.ExecuteSqlRawAsync(@"
DO $$ BEGIN DO $$ BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename='clubs' AND policyname='bypass_rls_policy') THEN IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename='clubs' AND policyname='bypass_rls_policy') THEN

View File

@@ -67,8 +67,13 @@ spec:
secretKeyRef: secretKeyRef:
name: workclub-secrets name: workclub-secrets
key: database-connection-string key: database-connection-string
- name: Keycloak__Url - name: Keycloak__Authority
valueFrom: valueFrom:
configMapKeyRef: configMapKeyRef:
name: workclub-config name: workclub-config
key: keycloak-url key: keycloak-authority
- name: Keycloak__Audience
valueFrom:
configMapKeyRef:
name: workclub-config
key: keycloak-audience

View File

@@ -9,6 +9,8 @@ data:
cors-origins: "http://localhost:3000" cors-origins: "http://localhost:3000"
api-base-url: "http://workclub-api" api-base-url: "http://workclub-api"
keycloak-url: "http://workclub-keycloak" keycloak-url: "http://workclub-keycloak"
keycloak-authority: "http://workclub-keycloak/realms/workclub"
keycloak-audience: "workclub-api"
keycloak-realm: "workclub" keycloak-realm: "workclub"
# Database configuration # Database configuration
@@ -39,3 +41,18 @@ data:
\c workclub \c workclub
GRANT ALL PRIVILEGES ON SCHEMA public TO app; GRANT ALL PRIVILEGES ON SCHEMA public TO app;
ALTER SCHEMA public OWNER TO app; ALTER SCHEMA public OWNER TO app;
-- App admin role for RLS bypass policies used by API startup seed
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'app_admin') THEN
CREATE ROLE app_admin;
END IF;
END
$$;
GRANT app_admin TO app WITH INHERIT FALSE, SET TRUE;
GRANT USAGE ON SCHEMA public TO app_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO app_admin;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO app_admin;
ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON TABLES TO app_admin;
ALTER DEFAULT PRIVILEGES FOR ROLE app IN SCHEMA public GRANT ALL ON SEQUENCES TO app_admin;

View File

@@ -7,6 +7,9 @@ metadata:
component: auth component: auth
spec: spec:
replicas: 1 replicas: 1
strategy:
type: Recreate
progressDeadlineSeconds: 1800
selector: selector:
matchLabels: matchLabels:
app: workclub-keycloak app: workclub-keycloak
@@ -21,25 +24,37 @@ spec:
image: quay.io/keycloak/keycloak:26.1 image: quay.io/keycloak/keycloak:26.1
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
args: args:
- start - start-dev
- --import-realm
ports: ports:
- name: http - name: http
containerPort: 8080 containerPort: 8080
protocol: TCP protocol: TCP
- name: management
containerPort: 9000
protocol: TCP
readinessProbe: readinessProbe:
httpGet: httpGet:
path: /health/ready path: /health/ready
port: http port: management
initialDelaySeconds: 150 initialDelaySeconds: 240
periodSeconds: 15 periodSeconds: 15
timeoutSeconds: 5 timeoutSeconds: 5
failureThreshold: 10 failureThreshold: 10
startupProbe:
httpGet:
path: /health/ready
port: management
initialDelaySeconds: 60
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 120
livenessProbe: livenessProbe:
httpGet: httpGet:
path: /health/live path: /health/live
port: http port: management
initialDelaySeconds: 240 initialDelaySeconds: 420
periodSeconds: 20 periodSeconds: 20
timeoutSeconds: 5 timeoutSeconds: 5
failureThreshold: 5 failureThreshold: 5
@@ -84,3 +99,11 @@ spec:
value: "true" value: "true"
- name: KC_HEALTH_ENABLED - name: KC_HEALTH_ENABLED
value: "true" value: "true"
volumeMounts:
- name: keycloak-realm-import
mountPath: /opt/keycloak/data/import
readOnly: true
volumes:
- name: keycloak-realm-import
configMap:
name: keycloak-realm-import

View File

@@ -0,0 +1,246 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: keycloak-realm-import
labels:
app: workclub-keycloak
data:
realm-export.json: |
{
"realm": "workclub",
"enabled": true,
"displayName": "Work Club Manager",
"registrationAllowed": false,
"rememberMe": true,
"verifyEmail": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"bruteForceProtected": true,
"clients": [
{
"clientId": "workclub-api",
"name": "Work Club API",
"enabled": true,
"protocol": "openid-connect",
"clientAuthenticatorType": "client-secret",
"secret": "dev-secret-workclub-api-change-in-production",
"redirectUris": [],
"webOrigins": [],
"publicClient": false,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"standardFlowEnabled": false,
"implicitFlowEnabled": false,
"fullScopeAllowed": true,
"protocolMappers": [
{
"name": "audience-workclub-api",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "workclub-api",
"id.token.claim": "false",
"access.token.claim": "true"
}
},
{
"name": "clubs-claim",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"user.attribute": "clubs",
"claim.name": "clubs",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
]
},
{
"clientId": "workclub-app",
"name": "Work Club Frontend",
"enabled": true,
"protocol": "openid-connect",
"publicClient": true,
"redirectUris": [
"http://localhost:3000/*",
"http://localhost:3001/*",
"http://workclub-frontend/*"
],
"webOrigins": [
"http://localhost:3000",
"http://localhost:3001",
"http://workclub-frontend"
],
"directAccessGrantsEnabled": true,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"fullScopeAllowed": true,
"protocolMappers": [
{
"name": "audience-workclub-api",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"consentRequired": false,
"config": {
"included.client.audience": "workclub-api",
"id.token.claim": "false",
"access.token.claim": "true"
}
},
{
"name": "clubs-claim",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"user.attribute": "clubs",
"claim.name": "clubs",
"jsonType.label": "String",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
}
]
}
],
"roles": {
"realm": [
{
"name": "admin",
"description": "Club admin"
},
{
"name": "manager",
"description": "Club manager"
},
{
"name": "member",
"description": "Club member"
},
{
"name": "viewer",
"description": "Club viewer"
}
]
},
"users": [
{
"username": "admin@test.com",
"enabled": true,
"email": "admin@test.com",
"firstName": "Admin",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "testpass123",
"temporary": false
}
],
"realmRoles": [
"admin"
],
"attributes": {
"clubs": [
"64e05b5e-ef45-81d7-f2e8-3d14bd197383,Admin,3b4afcfa-1352-8fc7-b497-8ab52a0d5fda,Member"
]
}
},
{
"username": "manager@test.com",
"enabled": true,
"email": "manager@test.com",
"firstName": "Manager",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "testpass123",
"temporary": false
}
],
"realmRoles": [
"manager"
],
"attributes": {
"clubs": [
"64e05b5e-ef45-81d7-f2e8-3d14bd197383,Manager"
]
}
},
{
"username": "member1@test.com",
"enabled": true,
"email": "member1@test.com",
"firstName": "Member",
"lastName": "One",
"credentials": [
{
"type": "password",
"value": "testpass123",
"temporary": false
}
],
"realmRoles": [
"member"
],
"attributes": {
"clubs": [
"64e05b5e-ef45-81d7-f2e8-3d14bd197383,Member,3b4afcfa-1352-8fc7-b497-8ab52a0d5fda,Member"
]
}
},
{
"username": "member2@test.com",
"enabled": true,
"email": "member2@test.com",
"firstName": "Member",
"lastName": "Two",
"credentials": [
{
"type": "password",
"value": "testpass123",
"temporary": false
}
],
"realmRoles": [
"member"
],
"attributes": {
"clubs": [
"64e05b5e-ef45-81d7-f2e8-3d14bd197383,Member"
]
}
},
{
"username": "viewer@test.com",
"enabled": true,
"email": "viewer@test.com",
"firstName": "Viewer",
"lastName": "User",
"credentials": [
{
"type": "password",
"value": "testpass123",
"temporary": false
}
],
"realmRoles": [
"viewer"
],
"attributes": {
"clubs": [
"64e05b5e-ef45-81d7-f2e8-3d14bd197383,Viewer"
]
}
}
]
}

View File

@@ -9,6 +9,10 @@ resources:
- postgres-statefulset.yaml - postgres-statefulset.yaml
- postgres-service.yaml - postgres-service.yaml
- keycloak-deployment.yaml - keycloak-deployment.yaml
- keycloak-realm-import-configmap.yaml
- keycloak-service.yaml - keycloak-service.yaml
- configmap.yaml - configmap.yaml
- ingress.yaml - ingress.yaml
generatorOptions:
disableNameSuffixHash: true

View File

@@ -11,10 +11,10 @@ commonLabels:
environment: development environment: development
images: images:
- name: workclub-api - name: 192.168.241.13:8080/workclub-api
newName: 192.168.241.13:8080/workclub-api newName: 192.168.241.13:8080/workclub-api
newTag: dev newTag: dev
- name: workclub-frontend - name: 192.168.241.13:8080/workclub-frontend
newName: 192.168.241.13:8080/workclub-frontend newName: 192.168.241.13:8080/workclub-frontend
newTag: dev newTag: dev