# services/jenkins/helmrelease.yaml apiVersion: helm.toolkit.fluxcd.io/v2 kind: HelmRelease metadata: name: jenkins namespace: jenkins spec: interval: 30m chart: spec: chart: jenkins version: 5.8.114 sourceRef: kind: HelmRepository name: jenkins namespace: flux-system install: remediation: retries: 3 upgrade: remediation: retries: 3 remediateLastFailure: true cleanupOnFail: true values: controller: jenkinsUrl: https://ci.bstein.dev ingress: enabled: true hostName: ci.bstein.dev ingressClassName: traefik annotations: cert-manager.io/cluster-issuer: letsencrypt traefik.ingress.kubernetes.io/router.entrypoints: websecure tls: - secretName: jenkins-tls hosts: - ci.bstein.dev installPlugins: - kubernetes - workflow-aggregator - git - configuration-as-code - oic-auth - job-dsl - configuration-as-code-support containerEnv: - name: ENABLE_OIDC value: "true" - name: OIDC_ISSUER value: "https://sso.bstein.dev/realms/atlas" - name: OIDC_CLIENT_ID valueFrom: secretKeyRef: name: jenkins-oidc key: clientId - name: OIDC_CLIENT_SECRET valueFrom: secretKeyRef: name: jenkins-oidc key: clientSecret - name: OIDC_AUTH_URL valueFrom: secretKeyRef: name: jenkins-oidc key: authorizationUrl - name: OIDC_TOKEN_URL valueFrom: secretKeyRef: name: jenkins-oidc key: tokenUrl - name: OIDC_USERINFO_URL valueFrom: secretKeyRef: name: jenkins-oidc key: userInfoUrl - name: OIDC_LOGOUT_URL valueFrom: secretKeyRef: name: jenkins-oidc key: logoutUrl customInitContainers: - name: clean-jcasc-stale image: alpine:3.20 imagePullPolicy: IfNotPresent command: - sh - -c - | set -euo pipefail rm -f /var/jenkins_home/casc_configs/*.yaml || true securityContext: runAsUser: 0 volumeMounts: - name: jenkins-home mountPath: /var/jenkins_home 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 import hudson.security.FullControlOnceLoggedInAuthorizationStrategy def env = System.getenv() 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_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'], Secret.fromString(env['OIDC_CLIENT_SECRET']), serverCfg, false, IdStrategy.CASE_INSENSITIVE, IdStrategy.CASE_INSENSITIVE ) realm.createProxyAwareResourceRetriver() 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) def auth = new FullControlOnceLoggedInAuthorizationStrategy() auth.setAllowAnonymousRead(false) j.setAuthorizationStrategy(auth) j.save() println("Configured OIDC realm from init script (well-known)") } catch (Exception e) { println("Failed to configure OIDC realm: ${e}") } JCasC: configScripts: creds.yaml: | credentials: system: domainCredentials: - credentials: - usernamePassword: scope: GLOBAL id: gitea-pat username: "bstein" password: "4693a39ee3f0ebb58e7d1795ab98add6df44ef12" description: "Gitea PAT for harbor-arm-build" - usernamePassword: scope: GLOBAL id: harbor-robot username: "robot$infra+robotuser-pipeline" password: "ouuvMheoTxOQtFSbWnO1OKVujORMPfO7" description: "Harbor robot for pipeline push" jobs.yaml: | jobs: - script: | pipelineJob('harbor-arm-build') { definition { cpsScm { scm { git { remote { url('https://scm.bstein.dev/bstein/harbor-arm-build.git') credentials('gitea-pat') } branches('*/master') } } } } } persistence: enabled: true storageClass: astreae size: 50Gi serviceAccount: create: true