Rotate gitea INTERNAL_TOKEN and JWT_SECRET into separate claims

Post-breach (CVE-2026-59774) rotation. All three security secrets shared
one generated value via the gitea-random SecretClaim, and the file-read
exploit leaked it. Split INTERNAL_TOKEN and JWT_SECRET into their own
claims (gitea-internal-token, gitea-oauth2-jwt) with fresh distinct
values and repoint the StatefulSet at them.

INTERNAL_TOKEN is the secret the attacker actually used against the
still-internet-reachable /api/internal, so this closes reuse of any saved
copy. Rotating JWT_SECRET invalidates gitea-issued OAuth2 and LFS tokens,
so the OAuth2 integrations (Woodpecker, tea, credential helpers) need one
re-login.

SECRET_KEY is deliberately left on gitea-random: rotating it would break
the 2 push mirrors (encrypted creds) and must be done in a window that
re-enters those credentials.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Erki Aas
2026-09-15 22:23:56 +03:00
co-authored by Claude Opus 4.8
parent bcb637685a
commit 18034b7cd9
+31 -2
View File
@@ -16,6 +16,15 @@ spec:
# 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. # 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 apiVersion: codemowers.cloud/v1beta1
kind: SecretClaim kind: SecretClaim
metadata: metadata:
@@ -31,6 +40,26 @@ spec:
value: "%(plaintext)s" value: "%(plaintext)s"
--- ---
apiVersion: codemowers.cloud/v1beta1 apiVersion: codemowers.cloud/v1beta1
kind: SecretClaim
metadata:
name: gitea-internal-token
spec:
size: 32
mapping:
- key: GITEA__SECURITY__INTERNAL_TOKEN
value: "%(plaintext)s"
---
apiVersion: codemowers.cloud/v1beta1
kind: SecretClaim
metadata:
name: gitea-oauth2-jwt
spec:
size: 32
mapping:
- key: GITEA__OAUTH2__JWT_SECRET
value: "%(plaintext)s"
---
apiVersion: codemowers.cloud/v1beta1
kind: OIDCClient kind: OIDCClient
metadata: metadata:
name: gitea name: gitea
@@ -220,12 +249,12 @@ spec:
- name: GITEA__OAUTH2__JWT_SECRET - name: GITEA__OAUTH2__JWT_SECRET
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-random name: gitea-oauth2-jwt
key: GITEA__OAUTH2__JWT_SECRET key: GITEA__OAUTH2__JWT_SECRET
- name: GITEA__SECURITY__INTERNAL_TOKEN - name: GITEA__SECURITY__INTERNAL_TOKEN
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
name: gitea-random name: gitea-internal-token
key: GITEA__SECURITY__INTERNAL_TOKEN key: GITEA__SECURITY__INTERNAL_TOKEN
- name: GITEA__SECURITY__SECRET_KEY - name: GITEA__SECURITY__SECRET_KEY
valueFrom: valueFrom: