Files
platform-core/services/gitea/README.md
T

12 KiB

Gitea 서비스

Gitea는 같은 공식 Helm Chart와 공통 values를 사용하는 두 Kustomize 빌드 루트로 관리한다. 업스트림 Chart 템플릿은 복제하거나 수정하지 않는다.

용도 빌드 루트 검증 산출물 적용 진입점
신규 설치 baseline services/gitea gitea.yaml apply-phase1-gitea.sh
OIDC·브랜딩 목표 상태 services/gitea/profiles/oidc gitea-oidc.yaml apply-gitea-oidc.sh

baseline 루트는 OIDC Secret, id host alias, Keycloak 전용 egress와 브랜딩을 포함하지 않는다. OIDC profile만 공통 기반에 이 네 요소를 추가한다.

상태(2026-07-23): Host Nginx public discovery 전환과 OIDC-enabled Gitea rollout을 완료했다. 적용 manifest SHA-256은 d25a757...a157이며, 활성 keycloak OAuth2 source, 외부 인증 전용 가입 정책, authorization-code redirect와 아래 선언형 브랜딩의 자동 검증을 모두 통과했다. 실제 realm 사용자의 브라우저 login/callback/logout, 비상 관리자 실제 로그인과 Pod 재시작 뒤 설정 지속성은 별도 수용 시험으로 남아 있다.

고정된 업스트림 릴리스

항목
Chart 저장소 https://dl.gitea.com/charts/
Chart gitea
Chart 버전 12.7.0
Chart 패키지 SHA-256 5881ef9c59400bee2d5547e77c4cd0efb925143c2f5d93fb4f38446db76b0167
Gitea 이미지 docker.gitea.com/gitea:1.27.0-rootless

Chart는 렌더링할 때 저장소 공통 .gitignore에서 제외한 profiles/oidc/.helm/charts에 내려받는다. baseline 루트와 OIDC profile이 이 고정 SHA 검증 캐시 하나를 각자의 chartHome으로 참조한다. values와 Patch는 모두 services/gitea 아래에 있으므로 Kustomize의 기본 RootOnly 로드 제한을 유지한다.

baseline과 OIDC 로그인 정책

두 프로필이 공유하는 profiles/oidc/values/baseline.yaml은 다음 정책을 선언한다.

  • 자체 가입을 닫고(DISABLE_REGISTRATION=true) 외부 인증 전용 가입도 끈다.
  • 가입 버튼을 숨기고 기존 gitea-admin 로컬 비상 로그인을 유지한다.
  • PostgreSQL, PVC, probe, resource와 restricted securityContext를 공통으로 둔다.

profiles/oidc/values/oidc.yaml은 baseline 위에 다음 항목만 덮어쓴다.

  • APP_NAME=Hyeonworks Git으로 브라우저 제목과 애플리케이션 이름을 고정한다.
  • DISABLE_REGISTRATION=falseALLOW_ONLY_EXTERNAL_REGISTRATION=true를 함께 사용해 Keycloak 같은 외부 인증을 통한 신규 사용자 생성만 허용한다.
  • SHOW_REGISTRATION_BUTTON=false로 Gitea 자체 공개 가입 버튼을 숨긴다.
  • oauth2_client.ENABLE_AUTO_REGISTRATION=true, USERNAME=preferred_username, OPENID_CONNECT_SCOPES="profile email"로 OIDC 최초 로그인 계정을 생성한다.
  • baseline의 ENABLE_PASSWORD_SIGNIN_FORM=true를 유지해 Keycloak 장애 시에도 로컬 비상 관리자가 로그인할 수 있게 한다.

ACCOUNT_LINKING=login은 이메일이 같은 기존 계정을 자동 병합하지 않는다. 사용자가 기존 Gitea 자격 증명으로 한 번 더 확인해야 연결되므로, 이메일 일치만으로 계정을 탈취하는 위험을 줄인다.

의존성과 트래픽

  • giteaplatform-data namespace를 먼저 적용한다.
  • 이 서비스를 적용하기 전에 /srv/k3s/ssd/gitea를 준비하고 gitea-local-pv 기반 구성을 적용한다. gitea-data Claim은 ssd-local-gitea-retain을 통해 해당 PV에 바인딩되며 50Gi를 요청한다.
  • 외부 Database endpoint는 platform-postgres-rw.platform-data.svc.cluster.local:5432다.
  • Host Nginx에서 공개 TLS를 종료한다. Traefik은 HTTP를 받아 gitea-http:3000으로 전달하며, 이 Ingress에는 의도적으로 TLS 섹션이 없다.
  • OIDC profile의 Gitea 서버가 discovery와 token endpoint에 접근할 때도 공개 issuer https://id.learn.hyeonworks.com/realms/hyeonworks를 사용한다. 홈 라우터의 NAT loopback에 의존하지 않도록 Pod의 hostAliases는 이 이름만 노드 192.168.0.107로 해석하고, 전용 NetworkPolicy가 해당 /32의 TCP 443만 허용한다. 연결은 Host Nginx TLS와 Traefik을 그대로 통과한다.
  • 첫 배포에서는 Gitea SSH, Actions, Git LFS 및 Package Registry를 비활성화한다. Chart 12.7.0은 항상 SSH Service를 렌더링하므로 baseline 루트는 inline patch, OIDC profile은 patches/delete-ssh-service.yaml로 Helm 렌더링 뒤 gitea-ssh를 삭제한다.
  • 공통 egress는 클러스터 DNS, 플랫폼 PostgreSQL Pod 및 공개 IPv4 HTTPS 대상으로 제한한다. OIDC profile만 Host Nginx 노드 /32의 TCP 443을 추가한다. 내부 HTTPS, SMTP, SSH remote 및 사설 네트워크 webhook을 사용하려면 향후 NetworkPolicy를 명시적으로 변경해야 한다.

선언형 브랜딩

브랜딩은 Gitea 공식 custom path와 Chart의 volume 확장 필드만 사용한다. Chart 템플릿과 Gitea 전체 페이지 템플릿은 복제하거나 수정하지 않는다.

branding/
├── public/assets/
│   ├── css/hyeonworks.css
│   └── img/
│       ├── logo.svg
│       └── favicon.svg
└── templates/custom/
    ├── header.tmpl
    └── extra_links.tmpl

branding/kustomization.yamlconfigMapGenerator가 자산과 템플릿 ConfigMap을 각각 생성하고 profiles/oidc/kustomization.yaml만 이 구성요소를 포함한다. 이름 suffix hash를 끄지 않았으므로 파일 내용이 바뀌면 이름도 바뀌고, Kustomize가 Deployment의 ConfigMap 참조를 같은 해시 이름으로 치환한다.

profiles/oidc/values/oidc.yaml은 공식 Chart 확장 지점을 다음과 같이 사용한다.

  • 자산 ConfigMap을 /data/gitea/public/assets에 마운트한다.
  • 템플릿 ConfigMap을 /data/gitea/templates/custom에 마운트한다.
  • 두 volume은 rootless Gitea 주 컨테이너에만 readOnly: true로 마운트하며, init container에는 추가하지 않는다.

header.tmpl은 같은 origin의 CSS와 모바일 브라우저 색상만 추가한다. extra_links.tmpl은 실제 Gitea 내부 경로인 /explore/organizations만 연결한다. 존재하지 않는 Learning 또는 Identity 서비스 링크는 미리 만들지 않는다. 전체 home.tmpl은 덮어쓰지 않아 Gitea 업그레이드 때 template drift 범위를 줄인다. Gitea는 custom template을 시작할 때 읽으므로 변경 적용에는 Pod 재시작이 필요하다.

SVG 원본만 저장소에서 관리한다. Gitea가 참조하는 Open Graph, Apple touch icon 및 구형 브라우저용 PNG는 검증된 변환 절차를 추가하기 전까지 업스트림 기본 자산을 사용한다.

Secret 계약

credential Secret 매니페스트나 credential 값은 이 저장소에 두지 않는다. gitea-admingitea-db-credentials는 baseline Deployment 최초 시작 전에, gitea-keycloak-oidc는 OIDC-enabled Deployment rollout 전에 gitea namespace에 있어야 한다.

gitea-admin

속성 필수 값
Kubernetes 유형 kubernetes.io/basic-auth
username 최초 로컬 비상 관리자 username
password 강력한 일회용 관리자 비밀번호

Chart는 initialOnlyRequireReset을 사용하므로 최초 로그인 때 초기 비밀번호를 변경해야 하며 이후에는 조정하지 않는다.

gitea-db-credentials

속성 필수 값
Kubernetes 유형 kubernetes.io/basic-auth
username gitea
password PostgreSQL gitea Role에 할당한 비밀번호

Kubernetes에서는 namespace를 넘어 Secret을 참조할 수 없다. platform-data에 있는 같은 이름의 Secret은 CloudNativePG/bootstrap 사본이다. gitea namespace의 Secret은 동일한 Database credential을 담아야 하며 Chart가 이 사본을 참조한다.

gitea-keycloak-oidc

속성 필수 값
Kubernetes 유형 Opaque
key gitea
secret Keycloak이 생성한 confidential client secret

scripts/bootstrap/configure-keycloak-gitea-oidc.sh가 Keycloak Admin REST API로 정확한 callback URI를 가진 client를 생성하거나 갱신하고 이 Secret을 만든다. 공식 Chart의 gitea.oauth[].existingSecretkeysecret을 환경 변수로만 초기화 컨테이너에 전달하고, 인증 소스 이름 keycloak을 반복 실행 시 갱신한다. Secret payload는 values, 렌더 결과 또는 Git에 포함되지 않는다.

업스트림 Chart는 credential이 아닌 초기화 스크립트와 inline 설정이 담긴 Opaque Secret도 렌더링한다. 이 생성 리소스들은 고정된 Chart의 일부이며 위 세 credential 계약의 값은 포함하지 않는다.

렌더링과 검사

Helm은 PATH에 있어야 하지만 릴리스를 직접 설치하는 데 사용하지 않는다. 플랫폼 저장소 루트에서 고정 Chart SHA를 확인하는 검증기로 렌더링한다.

PLATFORM_HELM_BIN=/home/donghyeon/.local/bin/helm \
  bash scripts/validate/render-phase1.sh

검증기는 gitea.yaml baseline과 gitea-oidc.yaml 목표 상태를 모두 렌더한다. 두 결과에는 각각 Gitea Deployment 하나, gitea-http, gitea-data, Traefik Ingress가 있어야 하고 gitea-ssh, NodePort, LoadBalancer와 내부 TLS는 없어야 한다. 두 Deployment 모두 gitea-admin, gitea-db-credentials를 참조만 하고 생성하지 않는다.

baseline은 gitea-keycloak-oidc, ID host alias, Keycloak 전용 NetworkPolicy, 브랜딩 ConfigMap과 외부 인증 전용 가입 설정이 없어야 한다. 반대로 OIDC 산출물은 gitea-keycloak-oidc를 값 노출 없이 참조하고 다음을 모두 만족해야 한다.

  • 두 브랜딩 ConfigMap 이름에 content hash가 있고 Deployment 참조와 일치한다.
  • /data/gitea/public/assets/data/gitea/templates/custom 마운트는 read-only다.
  • inline 설정에 외부 인증 전용 가입, OIDC 자동 계정 생성, ACCOUNT_LINKING=login, 로컬 비밀번호 로그인 유지가 모두 렌더링된다.
  • OAuth 인증 소스 이름은 callback URI의 경로와 같은 keycloak이고 discovery issuer는 https://id.learn.hyeonworks.com/realms/hyeonworks다.

Keycloak OIDC 안전 적용

Host Nginx의 Keycloak discovery가 실제 JSON issuer를 반환하고 위 세 Secret 계약이 준비된 뒤 다음 전용 진입점으로 적용한다.

  1. Host Nginx의 id.learn.hyeonworks.com 정적 hold를 Keycloak proxy 후보로 전환한다.
  2. public discovery JSON의 issuerhttps://id.learn.hyeonworks.com/realms/hyeonworks와 정확히 같은지 확인한다.
  3. 다음 전용 스크립트로 OIDC 목표 상태와 브랜딩을 함께 적용한다.
PLATFORM_HELM_BIN=/home/donghyeon/.local/bin/helm \
  bash scripts/bootstrap/apply-gitea-oidc.sh --execute

스크립트는 public discovery와 Secret의 type·key 이름만 먼저 확인하고, 고정 Chart SHA 검증 결과 중 gitea-oidc.yaml 하나만 적용한다. 이후 Deployment·EndpointSlice, 활성 keycloak OAuth2 source, 외부 인증 전용 가입 정책, OIDC authorization-code redirect와 브랜딩 자산 SHA-256을 검증한다. Secret 값, bearer token과 OIDC state가 포함된 전체 Location은 출력하지 않는다.

2026-07-23 실제 적용에서는 manifest SHA-256 d25a757...a157의 rollout과 위 자동 검사를 모두 통과했다. 이 redirect 검사는 realm 사용자의 실제 브라우저 login/callback/logout 시험을 대신하지 않는다. self-registration은 비활성이므로 named 관리자와 MFA를 준비한 뒤 시험 사용자를 별도로 생성한다. 로컬 비상 관리자 실제 로그인과 Pod 재시작 뒤 OAuth source·정책·브랜딩 지속성도 별도로 확인한다.

기존 Gitea에 apply-phase1-gitea.sh를 다시 실행해 OIDC 상태를 적용하거나 복구하지 않는다.