Files
kube/gitea/application.yaml
T
Erki AasandClaude Opus 4.8 e17b0e0b41 Make gitea OAuth2 JWT_SECRET a valid, stable 43-char key
GITEA__OAUTH2__JWT_SECRET must base64url-decode to 32 bytes. The size-32
claim decoded to 24 bytes, so 1.27 rejected it and generated its own key
on each boot; since app.ini lives on an emptyDir that regenerated every
restart and logged out all OAuth2 clients. size 43 decodes to 32 bytes,
so the rotated key is accepted and stays stable across restarts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-15 22:38:10 +03:00

327 lines
11 KiB
YAML

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: gitea
namespace: gitea
spec:
dnsNames:
- git.k-space.ee
issuerRef:
group: cert-manager.io
kind: ClusterIssuer
name: default
secretName: git-tls
revisionHistoryLimit: 1
# Gitea DOES NOT go through Traefik. It has its own IP because ssh :22 would conflict with kube worker ssh. On its own IP, at the moment it doesn't flirt with Traefik — also has its own certificate.
---
# After the 2026-09 breach (CVE-2026-59774) all three secrets below leaked
# because they shared one generated value. INTERNAL_TOKEN and JWT_SECRET were
# moved to their own claims (gitea-internal-token, gitea-oauth2-jwt) and rotated;
# the StatefulSet now reads those two from the new claims, so the copies here are
# unused. SECRET_KEY is intentionally still sourced from this claim and NOT
# rotated: a new value would make data-at-rest (push-mirror creds, 2FA)
# undecryptable and break the 2 push mirrors, whose credentials must be re-entered
# by hand. Do not shrink this mapping: the operator would regenerate the plaintext
# and change SECRET_KEY. Rotate SECRET_KEY in a maintenance window instead.
apiVersion: codemowers.cloud/v1beta1
kind: SecretClaim
metadata:
name: gitea-random
spec:
size: 32
mapping:
- key: GITEA__SECURITY__SECRET_KEY
value: "%(plaintext)s"
- key: GITEA__SECURITY__INTERNAL_TOKEN
value: "%(plaintext)s"
- key: GITEA__OAUTH2__JWT_SECRET
value: "%(plaintext)s"
---
apiVersion: codemowers.cloud/v1beta1
kind: SecretClaim
metadata:
name: gitea-internal-token
spec:
size: 32
mapping:
- key: GITEA__SECURITY__INTERNAL_TOKEN
value: "%(plaintext)s"
---
# size 43: GITEA__OAUTH2__JWT_SECRET must base64url-decode to exactly 32 bytes.
# A 43-char value decodes to 32 bytes; a 32-char one decodes to 24 and Gitea
# rejects it and generates its own key each boot, which (app.ini is on an
# emptyDir) changes on every restart and logs out all OAuth2 clients. 43 keeps
# the rotated key valid and stable.
apiVersion: codemowers.cloud/v1beta1
kind: SecretClaim
metadata:
name: gitea-oauth2-jwt
spec:
size: 43
mapping:
- key: GITEA__OAUTH2__JWT_SECRET
value: "%(plaintext)s"
---
apiVersion: codemowers.cloud/v1beta1
kind: OIDCClient
metadata:
name: gitea
spec:
displayName: Gitea
uri: https://git.k-space.ee/user/oauth2/OpenID
redirectUris:
- https://git.k-space.ee/user/oauth2/OpenID/callback
allowedGroups:
- k-space:floor
- k-space:friends
grantTypes:
- authorization_code
- refresh_token
responseTypes:
- code
availableScopes:
- openid
- profile
overrideIncomingScopes: true
pkce: false
secretRefreshPod:
apiVersion: v1
kind: Pod
metadata:
name: reset-oidc-config
spec:
volumes:
- name: tmp
emptyDir: {}
initContainers:
- name: jq
image: mirror.gcr.io/alpine/k8s:1.31.76@sha256:2a3fdd639c71c6cad69fbc8cac2467648855dac29961efec3b155466cc4fa730
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /tmp
name: tmp
envFrom:
- secretRef:
name: oidc-client-gitea-owner-secrets
command:
- /bin/bash
- -c
- jq '{"strategyKey":"OpenID","config":{"Provider":"openidConnect","ClientID":$ENV.OIDC_CLIENT_ID,"ClientSecret":$ENV.OIDC_CLIENT_SECRET,"OpenIDConnectAutoDiscoveryURL":"https://auth.k-space.ee/.well-known/openid-configuration","CustomURLMapping":null,"IconURL":"","Scopes":null,"RequiredClaimName":"","RequiredClaimValue":"","GroupClaimName":"","AdminGroup":"","GroupTeamMap":"","GroupTeamMapRemoval":false,"RestrictedGroup":""}} | "UPDATE login_source SET cfg=\(.config|tostring|@sh) WHERE name=\(.strategyKey|tostring|@sh) LIMIT 1"' -n -r > /tmp/update.sql
containers:
- name: mysql
image: mirror.gcr.io/library/mysql:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /tmp
name: tmp
env:
- name: MYSQL_PWD
valueFrom:
secretKeyRef:
name: mariadb-secrets
key: MYSQL_PASSWORD
command:
- /bin/bash
- -c
- mysql -u gitea gitea -h mariadb -p${MYSQL_PWD} < /tmp/update.sql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: gitea
labels:
app.kubernetes.io/name: gitea
spec:
revisionHistoryLimit: 0
serviceName: gitea
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: gitea
template:
metadata:
labels:
app.kubernetes.io/name: gitea
spec:
enableServiceLinks: false
securityContext:
fsGroup: 1000
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
containers:
- name: gitea
# https://github.com/go-gitea/gitea/releases
image: docker.gitea.com/gitea:1.27.3-rootless
imagePullPolicy: IfNotPresent
securityContext:
readOnlyRootFilesystem: true
env:
- name: GITEA__REPOSITORY__DISABLED_REPO_UNITS
value: repo.releases,repo.wiki
- name: GITEA__ADMIN__DISABLE_REGULAR_ORG_CREATION
value: "true"
- name: GITEA__SERVER__SSH_SERVER_HOST_KEYS
value: ssh/gitea.rsa,ssh/gitea.ecdsa,ssh/gitea.ed25519
- name: GITEA__SERVER__START_SSH_SERVER
value: "true"
- name: GITEA__SERVER__CERT_FILE
value: "/cert/tls.crt"
- name: GITEA__SERVER__KEY_FILE
value: "/cert/tls.key"
- name: GITEA__SERVER__SSH_PORT
value: "22"
# Port advertised in clone URLs is 22, but the rootless container
# cannot bind it, so the builtin server listens on 2222 and the
# Service maps 22 -> 2222. Until 1.26 the image defaulted
# SSH_LISTEN_PORT to SSH_PORT; it is empty now, which makes Gitea
# fall back to SSH_PORT and crash with "bind: permission denied".
- name: GITEA__SERVER__SSH_LISTEN_PORT
value: "2222"
- name: GITEA__SERVER__PROTOCOL
value: https
- name: GITEA__SERVER__REDIRECT_OTHER_PORT
value: "true"
- name: GITEA__SERVER__PORT_TO_REDIRECT
value: "8080"
- name: GITEA__SERVER__DOMAIN
value: git.k-space.ee
- name: GITEA__SERVER__SSH_DOMAIN
value: git.k-space.ee
- name: GITEA__SERVER__HTTP_ADDR
value: 0.0.0.0
- name: GITEA__SERVER__ROOT_URL
value: https://git.k-space.ee
- name: GITEA__SSH.MINIMUM_KEY_SIZES__DSA
value: "-1"
- name: GITEA__DATABASE__DB_TYPE
value: mysql
- name: GITEA__DATABASE__HOST
value: mariadb:3306
- name: GITEA__DATABASE__NAME
value: gitea
- name: GITEA__DATABASE__USER
value: gitea
- name: GITEA__DATABASE__SSL_MODE
value: disable
- name: GITEA__DATABASE__LOG_SQL
value: "false"
- name: GITEA__SECURITY__INSTALL_LOCK
value: "true"
# Disable bypassing (disabled) OIDC account. Password-based app tokens remain enabled.
- name: GITEA__SERVICE__ENABLE_PASSWORD_SIGNIN_FORM
value: "false"
- name: GITEA__SERVICE__ENABLE_PASSKEY_AUTHENTICATION
value: "false"
- name: GITEA__SERVICE__REGISTER_EMAIL_CONFIRM
value: "true"
- name: GITEA__SERVICE__DISABLE_REGISTRATION
value: "true"
- name: GITEA__SERVICE__ENABLE_NOTIFY_MAIL
value: "true"
- name: GITEA__MAILER__ENABLED
value: "true"
- name: GITEA__MAILER__SMTP_ADDR
value: mail.k-space.ee
- name: GITEA__MAILER__SMTP_PORT
value: "465"
- name: GITEA__MAILER__FROM
value: Gitea <git@k-space.ee>
- name: GITEA__MAILER__USER
value: git
- name: GITEA__MAILER__USE_PLAIN_TEXT
value: "false"
- name: GITEA__SESSION__PROVIDER
value: file
- name: GITEA__SESSION__COOKIE_SECURE
value: "true"
- name: GITEA__CRON__ENABLED
value: "true"
- name: GITEA__OAUTH2_CLIENT__ENABLE_AUTO_REGISTRATION
value: "true"
- name: GITEA__DATABASE__PASSWD
valueFrom:
secretKeyRef:
name: mariadb-secrets
key: MYSQL_PASSWORD
- name: GITEA__MAILER__PASSWD
valueFrom:
secretKeyRef:
name: gitea-mail
key: GITEA__MAILER__PASSWD
- name: GITEA__OAUTH2__JWT_SECRET
valueFrom:
secretKeyRef:
name: gitea-oauth2-jwt
key: GITEA__OAUTH2__JWT_SECRET
- name: GITEA__SECURITY__INTERNAL_TOKEN
valueFrom:
secretKeyRef:
name: gitea-internal-token
key: GITEA__SECURITY__INTERNAL_TOKEN
- name: GITEA__SECURITY__SECRET_KEY
valueFrom:
secretKeyRef:
name: gitea-random
key: GITEA__SECURITY__SECRET_KEY
ports:
- containerPort: 8080
name: http
- containerPort: 3000
name: https
- containerPort: 2222
name: ssh
volumeMounts:
- mountPath: /tmp
name: tmp
- mountPath: /etc/gitea
name: etc
- mountPath: /cert
name: cert
- mountPath: /var/lib/gitea
name: data
volumes:
- name: tmp
emptyDir: {}
- name: etc
emptyDir: {}
- name: cert
secret:
secretName: git-tls
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: gitea
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Service
metadata:
name: gitea
namespace: gitea
annotations:
external-dns.alpha.kubernetes.io/hostname: git.k-space.ee
spec:
type: LoadBalancer
externalTrafficPolicy: Local
selector:
app.kubernetes.io/name: gitea
ports:
- port: 22
name: ssh
targetPort: 2222
- port: 80
name: http
targetPort: 8080
- port: 443
name: https
targetPort: 3000
sessionAffinity: ClientIP