diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index ec39b79..ffa8c15 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -53,6 +53,7 @@ jobs: -i /tmp/inventory.yml \ infra/ansible/playbook.yml \ -e "repo_url=https://github.com/${{ github.repository }}.git" \ + -e "branch=${{ github.ref_name }}" \ -e "genai_env_file=/tmp/genai.env" # ------------------------------------------------------------------ @@ -92,6 +93,8 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 + with: + driver: docker - name: Log in to GHCR uses: docker/login-action@v3 @@ -113,8 +116,6 @@ jobs: ${{ steps.img.outputs.repo }}:${{ github.sha }} ${{ steps.img.outputs.repo }}:latest build-args: ${{ matrix.build_args || '' }} - cache-from: type=gha - cache-to: type=gha,mode=max # ------------------------------------------------------------------ # Deploy to the RKE2 Kubernetes cluster via Helm. @@ -147,10 +148,22 @@ jobs: --from-env-file=/tmp/genai.env \ --dry-run=client -o yaml | kubectl apply -f - + - name: Unlock stuck Helm release (if any) + run: | + for secret in $(kubectl -n "$NAMESPACE" get secret \ + -l "owner=helm,name=team-devoops" -o name 2>/dev/null); do + status=$(kubectl -n "$NAMESPACE" get "$secret" \ + -o jsonpath='{.metadata.labels.status}' 2>/dev/null || echo "") + if [[ "$status" == pending-* || "$status" == "failed" ]]; then + echo "Deleting stuck Helm secret $secret (status=$status)" + kubectl -n "$NAMESPACE" delete "$secret" + fi + done + - name: Helm upgrade run: | helm upgrade --install team-devoops infra/helm/team-devoops \ --namespace "$NAMESPACE" \ --set global.image.tag=${{ github.sha }} \ - --set keycloak.hostname=ge83mom-devops26.stud.k8s.aet.cit.tum.de \ - --atomic --timeout 15m + --set keycloak.hostname=https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth \ + --rollback-on-failure --timeout 15m diff --git a/README.md b/README.md index 0e7608f..6d5eb8e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ The Spring Boot services and the GenAI service share a **PostgreSQL** database. | GenAI Service | `/api/v1/helper/…` | 5000 | Python 3.12, Flask, LangChain | | Web Client | `/` | 8080 | React, Vite | | Swagger UI | `/docs` | 8080 | swaggerapi/swagger-ui | +| Keycloak | `/auth` | 8080 | Keycloak 26 | | Traefik dashboard | `http://localhost:8080` (local only) | — | Traefik v3 | | PostgreSQL | internal only | 5432 | postgres:15 | @@ -250,20 +251,24 @@ All services are protected by [Keycloak 26](https://www.keycloak.org) via OIDC/J ### Local login -When running with Docker Compose, Keycloak is available at . The realm is auto-imported on first start from [`infra/keycloak/realm-config.json`](infra/keycloak/realm-config.json). +When running with Docker Compose, Keycloak is available at . The realm is auto-imported on first start from [`infra/keycloak/realm-config.json`](infra/keycloak/realm-config.json). The web client redirects to Keycloak automatically (`login-required` strategy). Log in with any of the test users above. +### Production admin console + +Keycloak is publicly accessible via Traefik at . Admin console: `/auth/admin`. + ### Spring services — JWT validation Each Spring service is a stateless OAuth2 resource server. It validates Bearer JWTs against Keycloak's JWK set and extracts roles from the `realm_access.roles` claim, mapping them to Spring `ROLE_*` authorities (e.g. `"admin"` → `ROLE_admin`). -| Environment variable | Purpose | +| Property | Purpose | |---|---| -| `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI` | Validates the `iss` claim in incoming JWTs | -| `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI` | URL to fetch Keycloak's public signing keys | +| `spring.security.oauth2.resourceserver.jwt.issuer-uri` | Validates the `iss` claim in incoming JWTs | +| `spring.security.oauth2.resourceserver.jwt.jwk-set-uri` | URL to fetch Keycloak's public signing keys | -Docker Compose sets these to `http://keycloak:8080/auth/realms/devops/…`. On Kubernetes they are injected via the `env:` block in `infra/helm/team-devoops/values.yaml` using the internal `keycloak` ClusterIP DNS name. +These are set in each service's `src/main/resources/application.properties` as defaults (pointing at the local Keycloak on `localhost:8081/auth`). On the Azure VM, `docker-compose.yml` overrides `SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI` with the public HTTPS issuer so it matches the `iss` claim in tokens issued by production Keycloak. The JWK set URI always uses the internal Docker hostname `http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs`. On Kubernetes they are injected via the `env:` block in `infra/helm/team-devoops/values.yaml` using the internal `keycloak` ClusterIP DNS name. ## Docs diff --git a/infra/ansible/playbook.yml b/infra/ansible/playbook.yml index 77259c2..f8b6f49 100644 --- a/infra/ansible/playbook.yml +++ b/infra/ansible/playbook.yml @@ -76,11 +76,20 @@ force: true update: true + - name: Set repository ownership to ansible_user + file: + path: "{{ app_dir }}" + owner: "{{ ansible_user }}" + group: "{{ ansible_user }}" + recurse: true + - name: Write py-genai-helper .env file copy: src: "{{ genai_env_file }}" dest: "{{ app_dir }}/services/py-genai-helper/.env" mode: "0600" + owner: "{{ ansible_user }}" + group: "{{ ansible_user }}" - name: Deploy with docker compose shell: > diff --git a/infra/docker-compose.override.yml b/infra/docker-compose.override.yml index 57f1fc7..0be7631 100644 --- a/infra/docker-compose.override.yml +++ b/infra/docker-compose.override.yml @@ -40,6 +40,8 @@ services: - "traefik.http.services.py-genai-helper.loadbalancer.server.port=5000" organization-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.organization-service.entrypoints=web" @@ -49,6 +51,8 @@ services: - "traefik.http.services.organization-service.loadbalancer.server.port=8080" member-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.member-service.entrypoints=web" @@ -58,6 +62,8 @@ services: - "traefik.http.services.member-service.loadbalancer.server.port=8080" event-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.event-service.entrypoints=web" @@ -67,6 +73,8 @@ services: - "traefik.http.services.event-service.loadbalancer.server.port=8080" feedback-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.feedback-service.entrypoints=web" @@ -76,6 +84,8 @@ services: - "traefik.http.services.feedback-service.loadbalancer.server.port=8080" finance-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.finance-service.entrypoints=web" @@ -85,6 +95,8 @@ services: - "traefik.http.services.finance-service.loadbalancer.server.port=8080" letter-service: + environment: + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=http://localhost:8081/auth/realms/devops labels: !override - "traefik.enable=true" - "traefik.http.routers.letter-service.entrypoints=web" @@ -107,6 +119,13 @@ services: - "traefik.http.routers.web-client.rule=PathPrefix(`/`)" - "traefik.http.services.web-client.loadbalancer.server.port=8080" + keycloak: + environment: + KC_HOSTNAME: "http://localhost:8081/auth" + traefik-forward-auth: labels: !override - "traefik.enable=false" + environment: + - PROVIDERS_OIDC_ISSUER_URL=http://localhost:8081/auth/realms/devops + - INSECURE_COOKIE=true diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index fea6b1c..99c83db 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -33,6 +33,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.organization-service.entrypoints=websecure" @@ -59,6 +60,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.member-service.entrypoints=websecure" @@ -85,6 +87,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.event-service.entrypoints=websecure" @@ -111,6 +114,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.feedback-service.entrypoints=websecure" @@ -137,6 +141,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.finance-service.entrypoints=websecure" @@ -163,6 +168,7 @@ services: - SPRING_DATASOURCE_USERNAME=member_user - SPRING_DATASOURCE_PASSWORD=member_password - SPRING_JPA_HIBERNATE_DDL_AUTO=update + - SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops labels: - "traefik.enable=true" - "traefik.http.routers.letter-service.entrypoints=websecure" @@ -197,7 +203,10 @@ services: - proxy web-client: - build: ../web-client/ + build: + context: ../web-client/ + args: + VITE_KEYCLOAK_URL: https://team-devoops.uaenorth.cloudapp.azure.com/auth container_name: web-client expose: - 8080 @@ -211,8 +220,6 @@ services: - py-genai-helper labels: - "traefik.enable=true" - - "traefik.http.routers.web-client.entrypoints=web" - - "traefik.http.routers.web-client.rule=PathPrefix(`/`)" - "traefik.http.routers.web-client.entrypoints=websecure" - "traefik.http.routers.web-client.rule=Host(`team-devoops.uaenorth.cloudapp.azure.com`)" - "traefik.http.routers.web-client.tls=true" @@ -261,24 +268,22 @@ services: container_name: traefik-forward-auth environment: - DEFAULT_PROVIDER=oidc - - PROVIDERS_OIDC_ISSUER_URL=http://localhost:8081/realms/devops + - PROVIDERS_OIDC_ISSUER_URL=https://team-devoops.uaenorth.cloudapp.azure.com/auth/realms/devops - PROVIDERS_OIDC_CLIENT_ID=traefik-forward-auth - PROVIDERS_OIDC_CLIENT_SECRET=traefik-forward-auth-secret - SECRET=a-random-32-char-secret-changeme! - - INSECURE_COOKIE=true + - INSECURE_COOKIE=false + - LOG_LEVEL=debug extra_hosts: - - "localhost:host-gateway" + - "team-devoops.uaenorth.cloudapp.azure.com:host-gateway" labels: - - "traefik.enable=true" - - "traefik.http.routers.traefik-forward-auth.rule=Path(`/_oauth`)" - - "traefik.http.routers.traefik-forward-auth.entrypoints=web" - - "traefik.http.routers.traefik-forward-auth.middlewares=forward-auth@file" - - "traefik.http.services.traefik-forward-auth.loadbalancer.server.port=4181" + - "traefik.enable=false" depends_on: keycloak: condition: service_healthy networks: - proxy + restart: on-failure keycloak: image: quay.io/keycloak/keycloak:26.0.0 @@ -295,6 +300,17 @@ services: KC_BOOTSTRAP_ADMIN_USERNAME: admin KC_BOOTSTRAP_ADMIN_PASSWORD: admin KC_HEALTH_ENABLED: "true" + KC_HTTP_RELATIVE_PATH: /auth + KC_HTTP_MANAGEMENT_RELATIVE_PATH: / + KC_HOSTNAME: https://team-devoops.uaenorth.cloudapp.azure.com/auth + KC_PROXY_HEADERS: xforwarded + labels: + - "traefik.enable=true" + - "traefik.http.routers.keycloak.entrypoints=websecure" + - "traefik.http.routers.keycloak.rule=Host(`team-devoops.uaenorth.cloudapp.azure.com`) && PathPrefix(`/auth`)" + - "traefik.http.routers.keycloak.tls=true" + - "traefik.http.routers.keycloak.tls.certresolver=le" + - "traefik.http.services.keycloak.loadbalancer.server.port=8080" healthcheck: test: - "CMD-SHELL" @@ -302,7 +318,7 @@ services: interval: 10s timeout: 5s retries: 20 - start_period: 90s + start_period: 30s volumes: - ./keycloak/realm-config.json:/opt/keycloak/data/import/realm-config.json ports: diff --git a/infra/helm/team-devoops/files/realm-config.json b/infra/helm/team-devoops/files/realm-config.json index 916ab4d..091e210 100644 --- a/infra/helm/team-devoops/files/realm-config.json +++ b/infra/helm/team-devoops/files/realm-config.json @@ -68,8 +68,14 @@ "secret": "traefik-forward-auth-secret", "standardFlowEnabled": true, "directAccessGrantsEnabled": false, - "redirectUris": ["http://localhost/_oauth"], - "webOrigins": ["http://localhost"] + "redirectUris": [ + "https://team-devoops.uaenorth.cloudapp.azure.com/_oauth", + "http://localhost/_oauth" + ], + "webOrigins": [ + "https://team-devoops.uaenorth.cloudapp.azure.com", + "http://localhost" + ] } ] } diff --git a/infra/helm/team-devoops/templates/deployment.yaml b/infra/helm/team-devoops/templates/deployment.yaml index 4625437..978269c 100644 --- a/infra/helm/team-devoops/templates/deployment.yaml +++ b/infra/helm/team-devoops/templates/deployment.yaml @@ -8,6 +8,8 @@ metadata: {{- include "team-devoops.labels" (dict "name" $name "root" $root) | nindent 4 }} spec: replicas: {{ $svc.replicas | default 1 }} + strategy: + {{- toYaml $root.Values.strategy | nindent 4 }} selector: matchLabels: {{- include "team-devoops.selectorLabels" (dict "name" $name) | nindent 6 }} @@ -48,23 +50,33 @@ spec: {{- end }} {{- end }} {{- if $svc.health }} + startupProbe: + httpGet: + path: {{ $svc.health }} + port: {{ $svc.port }} + initialDelaySeconds: 20 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 30 readinessProbe: httpGet: path: {{ $svc.health }} port: {{ $svc.port }} - initialDelaySeconds: 15 periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 livenessProbe: httpGet: path: {{ $svc.health }} port: {{ $svc.port }} - initialDelaySeconds: 60 periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 {{- else }} readinessProbe: tcpSocket: port: {{ $svc.port }} - initialDelaySeconds: 15 + initialDelaySeconds: 30 periodSeconds: 10 {{- end }} resources: diff --git a/infra/helm/team-devoops/templates/keycloak-deployment.yaml b/infra/helm/team-devoops/templates/keycloak-deployment.yaml index 74d3c17..ef5cda9 100644 --- a/infra/helm/team-devoops/templates/keycloak-deployment.yaml +++ b/infra/helm/team-devoops/templates/keycloak-deployment.yaml @@ -15,6 +15,7 @@ spec: labels: {{- include "team-devoops.selectorLabels" (dict "name" "keycloak") | nindent 8 }} spec: + {{- if .Values.keycloak.db.enabled }} initContainers: - name: wait-for-db image: busybox:1.36 @@ -26,6 +27,14 @@ spec: echo "waiting for keycloak-database..." sleep 3 done + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + cpu: 50m + memory: 32Mi + {{- end }} containers: - name: keycloak image: {{ .Values.keycloak.image }} @@ -34,6 +43,7 @@ spec: ports: - containerPort: 8080 env: + {{- if .Values.keycloak.db.enabled }} - name: KC_DB value: postgres - name: KC_DB_URL_HOST @@ -45,6 +55,7 @@ spec: secretKeyRef: name: keycloak-credentials key: KC_DB_PASSWORD + {{- end }} - name: KC_BOOTSTRAP_ADMIN_USERNAME value: {{ .Values.keycloak.adminUsername | quote }} - name: KC_BOOTSTRAP_ADMIN_PASSWORD @@ -56,6 +67,8 @@ spec: value: "true" - name: KC_HTTP_RELATIVE_PATH value: {{ .Values.keycloak.path | quote }} + - name: KC_HTTP_MANAGEMENT_RELATIVE_PATH + value: "/" - name: KC_HOSTNAME value: {{ .Values.keycloak.hostname | quote }} - name: KC_PROXY_HEADERS @@ -66,20 +79,28 @@ spec: - name: realm-config mountPath: /opt/keycloak/data/import readOnly: true + startupProbe: + httpGet: + path: /health/ready + port: 9000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 60 readinessProbe: httpGet: - path: {{ printf "%s/health/ready" .Values.keycloak.path }} - port: 8080 - initialDelaySeconds: 120 + path: /health/ready + port: 9000 periodSeconds: 10 - failureThreshold: 12 + timeoutSeconds: 5 + failureThreshold: 3 livenessProbe: httpGet: - path: {{ printf "%s/health/live" .Values.keycloak.path }} - port: 8080 - initialDelaySeconds: 180 + path: /health/live + port: 9000 periodSeconds: 20 - failureThreshold: 5 + timeoutSeconds: 5 + failureThreshold: 3 resources: {{- toYaml .Values.keycloak.resources | nindent 12 }} volumes: diff --git a/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml b/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml index f521d3b..be5bee3 100644 --- a/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml +++ b/infra/helm/team-devoops/templates/keycloak-postgres-service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.keycloak.enabled }} +{{- if and .Values.keycloak.enabled .Values.keycloak.db.enabled }} # Headless service so the StatefulSet pod is reachable at :5432 inside the cluster. apiVersion: v1 kind: Service @@ -9,7 +9,7 @@ metadata: spec: clusterIP: None selector: - app: {{ .Values.keycloak.db.host }} + {{- include "team-devoops.selectorLabels" (dict "name" .Values.keycloak.db.host) | nindent 4 }} ports: - port: 5432 targetPort: 5432 diff --git a/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml b/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml index 5aee305..4e228ae 100644 --- a/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml +++ b/infra/helm/team-devoops/templates/keycloak-postgres-statefulset.yaml @@ -1,4 +1,4 @@ -{{- if .Values.keycloak.enabled }} +{{- if and .Values.keycloak.enabled .Values.keycloak.db.enabled }} apiVersion: apps/v1 kind: StatefulSet metadata: diff --git a/infra/helm/team-devoops/templates/keycloak-secret.yaml b/infra/helm/team-devoops/templates/keycloak-secret.yaml index fe41ce6..c4c4a56 100644 --- a/infra/helm/team-devoops/templates/keycloak-secret.yaml +++ b/infra/helm/team-devoops/templates/keycloak-secret.yaml @@ -7,7 +7,9 @@ metadata: {{- include "team-devoops.labels" (dict "name" "keycloak-credentials" "root" $) | nindent 4 }} type: Opaque stringData: + KC_BOOTSTRAP_ADMIN_PASSWORD: {{ .Values.keycloak.adminPassword | quote }} + {{- if .Values.keycloak.db.enabled }} KC_DB_PASSWORD: {{ .Values.keycloak.db.password | quote }} POSTGRES_PASSWORD: {{ .Values.keycloak.db.password | quote }} - KC_BOOTSTRAP_ADMIN_PASSWORD: {{ .Values.keycloak.adminPassword | quote }} + {{- end }} {{- end }} diff --git a/infra/helm/team-devoops/templates/keycloak-service.yaml b/infra/helm/team-devoops/templates/keycloak-service.yaml index ee9d182..cf1f26b 100644 --- a/infra/helm/team-devoops/templates/keycloak-service.yaml +++ b/infra/helm/team-devoops/templates/keycloak-service.yaml @@ -7,7 +7,7 @@ metadata: {{- include "team-devoops.labels" (dict "name" "keycloak" "root" $) | nindent 4 }} spec: selector: - app: keycloak + {{- include "team-devoops.selectorLabels" (dict "name" "keycloak") | nindent 4 }} ports: - port: 8080 targetPort: 8080 diff --git a/infra/helm/team-devoops/values.yaml b/infra/helm/team-devoops/values.yaml index 70171b4..f6a8c7c 100644 --- a/infra/helm/team-devoops/values.yaml +++ b/infra/helm/team-devoops/values.yaml @@ -29,11 +29,11 @@ database: storageSize: 5Gi resources: requests: - cpu: 250m - memory: 256Mi + cpu: 50m + memory: 128Mi limits: - cpu: "1" - memory: 512Mi + cpu: 150m + memory: 256Mi # py-genai-helper reads its configuration from a Secret created out-of-band by the # pipeline (kubectl create secret --from-env-file). Helm only references it by name. @@ -52,7 +52,7 @@ keycloak: enabled: true image: quay.io/keycloak/keycloak:26.0.0 path: /auth - hostname: ge83mom-devops26.stud.k8s.aet.cit.tum.de + hostname: https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth adminUsername: admin adminPassword: admin # Constrain JVM heap so Keycloak stays within its memory limit. @@ -62,9 +62,13 @@ keycloak: cpu: 100m memory: 256Mi limits: - cpu: 500m + cpu: 250m memory: 512Mi db: + # enabled: false means Keycloak uses its built-in H2 database (start-dev mode). + # The realm is re-imported from the ConfigMap on every pod start, so no + # user/client data is lost. Set to true only if you need persistent sessions. + enabled: true name: keycloak user: keycloak_user password: keycloak_password @@ -75,7 +79,7 @@ keycloak: cpu: 100m memory: 128Mi limits: - cpu: 250m + cpu: 150m memory: 256Mi ingress: @@ -91,14 +95,22 @@ ingress: # Adds the cert-manager.io/cluster-issuer annotation on the ingresses. clusterIssuer: letsencrypt-prod +# Rolling update strategy — maxSurge: 0 ensures the old pod is terminated before +# scheduling the new one, which is required to stay within the namespace CPU quota. +strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + # Default compute resources applied to every app container (overridable per service). resources: requests: cpu: 100m - memory: 256Mi + memory: 128Mi limits: - cpu: 500m - memory: 512Mi + cpu: 200m + memory: 384Mi # --------------------------------------------------------------------------- # Service catalogue. Keys are the Kubernetes object names. @@ -121,6 +133,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" member-service: path: /api/v1/members port: 8080 @@ -130,6 +143,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" event-service: path: /api/v1/events port: 8080 @@ -139,6 +153,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" feedback-service: path: /api/v1/feedback port: 8080 @@ -148,6 +163,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" finance-service: path: /api/v1/finance port: 8080 @@ -157,6 +173,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" letter-service: path: /api/v1/letters port: 8080 @@ -166,6 +183,7 @@ services: env: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: "https://ge83mom-devops26.stud.k8s.aet.cit.tum.de/auth/realms/devops" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: "http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs" + JAVA_TOOL_OPTIONS: "-Xmx300m -Xms64m" py-genai-helper: path: /api/v1/helper port: 5000 @@ -173,14 +191,35 @@ services: health: /health stripPrefix: true envFromSecret: genai-env + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 200m + memory: 512Mi web-client: path: / port: 8080 db: false health: / stripPrefix: false + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi api-docs: path: /docs port: 8080 db: false stripPrefix: false + resources: + requests: + cpu: 50m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi diff --git a/infra/keycloak/realm-config.json b/infra/keycloak/realm-config.json index 916ab4d..091e210 100644 --- a/infra/keycloak/realm-config.json +++ b/infra/keycloak/realm-config.json @@ -68,8 +68,14 @@ "secret": "traefik-forward-auth-secret", "standardFlowEnabled": true, "directAccessGrantsEnabled": false, - "redirectUris": ["http://localhost/_oauth"], - "webOrigins": ["http://localhost"] + "redirectUris": [ + "https://team-devoops.uaenorth.cloudapp.azure.com/_oauth", + "http://localhost/_oauth" + ], + "webOrigins": [ + "https://team-devoops.uaenorth.cloudapp.azure.com", + "http://localhost" + ] } ] } diff --git a/services/spring-event/src/main/resources/application.properties b/services/spring-event/src/main/resources/application.properties index 96d94fd..670e53c 100644 --- a/services/spring-event/src/main/resources/application.properties +++ b/services/spring-event/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=event-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-feedback/src/main/resources/application.properties b/services/spring-feedback/src/main/resources/application.properties index f6a7fe3..d0381b2 100644 --- a/services/spring-feedback/src/main/resources/application.properties +++ b/services/spring-feedback/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=feedback-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-finance/src/main/resources/application.properties b/services/spring-finance/src/main/resources/application.properties index b0711a8..e8af8a3 100644 --- a/services/spring-finance/src/main/resources/application.properties +++ b/services/spring-finance/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=finance-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-letter/src/main/resources/application.properties b/services/spring-letter/src/main/resources/application.properties index f9155b9..d550a7f 100644 --- a/services/spring-letter/src/main/resources/application.properties +++ b/services/spring-letter/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=letter-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-member/src/main/resources/application.properties b/services/spring-member/src/main/resources/application.properties index e3f5a62..acd890e 100644 --- a/services/spring-member/src/main/resources/application.properties +++ b/services/spring-member/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=member-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/services/spring-organization/src/main/resources/application.properties b/services/spring-organization/src/main/resources/application.properties index f5616e4..8f1688e 100644 --- a/services/spring-organization/src/main/resources/application.properties +++ b/services/spring-organization/src/main/resources/application.properties @@ -1,4 +1,4 @@ spring.application.name=organization-service -spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/devops -spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/realms/devops/protocol/openid-connect/certs +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/auth/realms/devops +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak:8080/auth/realms/devops/protocol/openid-connect/certs diff --git a/web-client/src/features/payments/client.ts b/web-client/src/features/payments/client.ts index da9ec92..f3c7d00 100644 --- a/web-client/src/features/payments/client.ts +++ b/web-client/src/features/payments/client.ts @@ -1,3 +1,3 @@ import { createApiClient } from '@/lib/keycloak' -export const paymentsClient = createApiClient('/api/v1/finances') +export const paymentsClient = createApiClient('/api/v1/finance') diff --git a/web-client/src/lib/keycloak.ts b/web-client/src/lib/keycloak.ts index d73b355..a52d698 100644 --- a/web-client/src/lib/keycloak.ts +++ b/web-client/src/lib/keycloak.ts @@ -2,7 +2,7 @@ import Keycloak from 'keycloak-js' import axios, { type AxiosInstance } from 'axios' const keycloak = new Keycloak({ - url: import.meta.env.VITE_KEYCLOAK_URL || 'http://localhost:8081', + url: import.meta.env.VITE_KEYCLOAK_URL || 'http://localhost:8081/auth', realm: 'devops', clientId: 'devops-client', })