diff --git a/services/gitea/deployment.yaml b/services/gitea/deployment.yaml index 5dc0cee..b5c4ca9 100644 --- a/services/gitea/deployment.yaml +++ b/services/gitea/deployment.yaml @@ -55,6 +55,12 @@ spec: value: "master" - name: ROOT_URL value: "https://scm.bstein.dev" + - name: GITEA__service__ENABLE_OPENID_SIGNIN + value: "true" + - name: GITEA__service__ALLOW_ONLY_EXTERNAL_REGISTRATION + value: "true" + - name: GITEA__service__DISABLE_REGISTRATION + value: "false" - name: DB_TYPE value: "postgres" - name: DB_HOST diff --git a/services/gitea/kustomization.yaml b/services/gitea/kustomization.yaml index 36d6c23..9731b76 100644 --- a/services/gitea/kustomization.yaml +++ b/services/gitea/kustomization.yaml @@ -7,3 +7,4 @@ resources: - service.yaml - pvc.yaml - ingress.yaml + - oidc-job.yaml diff --git a/services/gitea/oidc-job.yaml b/services/gitea/oidc-job.yaml new file mode 100644 index 0000000..652d40f --- /dev/null +++ b/services/gitea/oidc-job.yaml @@ -0,0 +1,85 @@ +# services/gitea/oidc-job.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: gitea-oidc-bootstrap + namespace: gitea +spec: + ttlSecondsAfterFinished: 1800 + backoffLimit: 1 + template: + metadata: + labels: + app: gitea + job: gitea-oidc-bootstrap + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + affinity: + podAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app: gitea + topologyKey: kubernetes.io/hostname + restartPolicy: OnFailure + volumes: + - name: gitea-data + persistentVolumeClaim: + claimName: gitea-data + containers: + - name: gitea-oidc-bootstrap + image: gitea/gitea:1.23 + imagePullPolicy: IfNotPresent + volumeMounts: + - name: gitea-data + mountPath: /data + env: + - name: CLIENT_ID + valueFrom: + secretKeyRef: + name: gitea-oidc + key: client_id + - name: CLIENT_SECRET + valueFrom: + secretKeyRef: + name: gitea-oidc + key: client_secret + - name: DISCOVERY_URL + valueFrom: + secretKeyRef: + name: gitea-oidc + key: openid_auto_discovery_url + command: + - /bin/bash + - -c + - | + set -euo pipefail + APPINI=/data/gitea/conf/app.ini + BIN=/usr/local/bin/gitea + list="$($BIN -c "$APPINI" admin auth list)" + id=$(echo "$list" | awk '$2=="keycloak"{print $1}') + if [ -n "$id" ]; then + echo "Updating existing auth source id=$id" + $BIN -c "$APPINI" admin auth update-oauth \ + --id "$id" \ + --name keycloak \ + --provider openidConnect \ + --key "$CLIENT_ID" \ + --secret "$CLIENT_SECRET" \ + --auto-discover-url "$DISCOVERY_URL" \ + --scopes "openid profile email" \ + --group-claim-name groups + else + echo "Creating keycloak auth source" + $BIN -c "$APPINI" admin auth add-oauth \ + --name keycloak \ + --provider openidConnect \ + --key "$CLIENT_ID" \ + --secret "$CLIENT_SECRET" \ + --auto-discover-url "$DISCOVERY_URL" \ + --scopes "openid profile email" \ + --group-claim-name groups + fi diff --git a/services/gitops-ui/helmrelease.yaml b/services/gitops-ui/helmrelease.yaml index 974251c..86ae327 100644 --- a/services/gitops-ui/helmrelease.yaml +++ b/services/gitops-ui/helmrelease.yaml @@ -23,13 +23,10 @@ spec: remediateLastFailure: true cleanupOnFail: true values: + additionalArgs: + - --auth-methods=oidc adminUser: - create: true - createClusterRole: true - createSecret: true - username: admin - # bcrypt hash for temporary password "G1tOps!2025" (rotate after login) - passwordHash: "$2y$12$wDEOzR1Gc2dbvNSJ3ZXNdOBVFEjC6YASIxnZmHIbO.W1m0fie/QVi" + create: false ingress: enabled: true className: traefik @@ -45,5 +42,7 @@ spec: - secretName: gitops-ui-tls hosts: - cd.bstein.dev + oidcSecret: + create: false metrics: enabled: true diff --git a/services/gitops-ui/kustomization.yaml b/services/gitops-ui/kustomization.yaml index a86611a..6d8b8a7 100644 --- a/services/gitops-ui/kustomization.yaml +++ b/services/gitops-ui/kustomization.yaml @@ -7,3 +7,4 @@ resources: - helmrelease.yaml - certificate.yaml - networkpolicy-acme.yaml + - rbac.yaml diff --git a/services/gitops-ui/rbac.yaml b/services/gitops-ui/rbac.yaml new file mode 100644 index 0000000..492d0e6 --- /dev/null +++ b/services/gitops-ui/rbac.yaml @@ -0,0 +1,15 @@ +# services/gitops-ui/rbac.yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: gitops-admins + labels: + app.kubernetes.io/name: weave-gitops +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cluster-admin +subjects: + - kind: Group + name: admin + apiGroup: rbac.authorization.k8s.io diff --git a/services/jenkins/helmrelease.yaml b/services/jenkins/helmrelease.yaml index 2b8d3ac..d591b9f 100644 --- a/services/jenkins/helmrelease.yaml +++ b/services/jenkins/helmrelease.yaml @@ -44,7 +44,7 @@ spec: - oic-auth containerEnv: - name: ENABLE_OIDC - value: "false" + value: "true" - name: OIDC_ISSUER value: "https://sso.bstein.dev/realms/atlas" - name: OIDC_CLIENT_ID @@ -85,50 +85,45 @@ spec: optional: true initScripts: oidc.groovy: | + import hudson.util.Secret + import jenkins.model.IdStrategy import jenkins.model.Jenkins import org.jenkinsci.plugins.oic.OicSecurityRealm + import org.jenkinsci.plugins.oic.OicServerWellKnownConfiguration def env = System.getenv() - def enable = (env['ENABLE_OIDC'] ?: 'false').toBoolean() - if (!enable) { + if (!(env['ENABLE_OIDC'] ?: 'false').toBoolean()) { println("OIDC disabled (ENABLE_OIDC=false); keeping default security realm") return } - def required = ['OIDC_CLIENT_ID','OIDC_CLIENT_SECRET','OIDC_AUTH_URL','OIDC_TOKEN_URL','OIDC_USERINFO_URL'] + def required = ['OIDC_CLIENT_ID','OIDC_CLIENT_SECRET','OIDC_ISSUER'] if (!required.every { env[it] }) { println("OIDC enabled but missing vars: ${required.findAll { !env[it] }}") return } try { + def wellKnown = "${env['OIDC_ISSUER']}/.well-known/openid-configuration" + def serverCfg = new OicServerWellKnownConfiguration(wellKnown) + serverCfg.setScopesOverride('openid profile email') def realm = new OicSecurityRealm( env['OIDC_CLIENT_ID'], - env['OIDC_CLIENT_SECRET'], - env['OIDC_TOKEN_URL'], - env['OIDC_AUTH_URL'], - env['OIDC_USERINFO_URL'], - true, // logout from provider - env['OIDC_LOGOUT_URL'] ?: "", - "", // postLogoutRedirectUrl - "openid email profile", - "", // prompt - "preferred_username", - "name", - "email", - false, // disableSslVerification - true, // escapeHatchEnabled - "admin", - "", // escapeHatchSecret - "", // escapeHatchGroup - true, // loadUserInfo - true, // validateScopes - false, // allowUnsignedIdTokens - false, // enforceValidIssuers - env['OIDC_ISSUER'] ?: "", - false // disableUserInfoFetch + Secret.fromString(env['OIDC_CLIENT_SECRET']), + serverCfg, + false, + IdStrategy.CASE_INSENSITIVE, + IdStrategy.CASE_INSENSITIVE ) + realm.setLogoutFromOpenidProvider(true) + realm.setPostLogoutRedirectUrl('https://ci.bstein.dev') + realm.setUserNameField('preferred_username') + realm.setFullNameFieldName('name') + realm.setEmailFieldName('email') + realm.setGroupsFieldName('groups') + realm.setRootURLFromRequest(true) + realm.setSendScopesInTokenRequest(true) def j = Jenkins.get() j.setSecurityRealm(realm) j.save() - println("Configured OIDC realm from init script") + println("Configured OIDC realm from init script (well-known)") } catch (Exception e) { println("Failed to configure OIDC realm: ${e}") }