230 lines
12 KiB
Markdown
230 lines
12 KiB
Markdown
# 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=false`와 `ALLOW_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 자격 증명으로 한 번 더 확인해야 연결되므로, 이메일 일치만으로 계정을
|
|
탈취하는 위험을 줄인다.
|
|
|
|
## 의존성과 트래픽
|
|
|
|
- `gitea`와 `platform-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 전체 페이지 템플릿은 복제하거나 수정하지 않는다.
|
|
|
|
```text
|
|
branding/
|
|
├── public/assets/
|
|
│ ├── css/hyeonworks.css
|
|
│ └── img/
|
|
│ ├── logo.svg
|
|
│ └── favicon.svg
|
|
└── templates/custom/
|
|
├── header.tmpl
|
|
└── extra_links.tmpl
|
|
```
|
|
|
|
`branding/kustomization.yaml`의 `configMapGenerator`가 자산과 템플릿 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는 검증된 변환 절차를 추가하기 전까지 업스트림 기본 자산을
|
|
사용한다.
|
|
|
|
- [Gitea 1.27 커스터마이징 공식 문서](https://docs.gitea.com/administration/customizing-gitea)
|
|
- [Gitea Chart 12.7.0 공식 values](https://gitea.com/gitea/helm-chart/src/tag/v12.7.0/values.yaml)
|
|
|
|
## Secret 계약
|
|
|
|
credential Secret 매니페스트나 credential 값은 이 저장소에 두지 않는다.
|
|
`gitea-admin`과 `gitea-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[].existingSecret`이 `key`와 `secret`을 환경 변수로만
|
|
초기화 컨테이너에 전달하고, 인증 소스 이름 `keycloak`을 반복 실행 시 갱신한다.
|
|
Secret payload는 values, 렌더 결과 또는 Git에 포함되지 않는다.
|
|
|
|
업스트림 Chart는 credential이 아닌 초기화 스크립트와 inline 설정이 담긴 Opaque
|
|
Secret도 렌더링한다. 이 생성 리소스들은 고정된 Chart의 일부이며 위 세 credential
|
|
계약의 값은 포함하지 않는다.
|
|
|
|
## 렌더링과 검사
|
|
|
|
Helm은 `PATH`에 있어야 하지만 릴리스를 직접 설치하는 데 사용하지 않는다. 플랫폼
|
|
저장소 루트에서 고정 Chart SHA를 확인하는 검증기로 렌더링한다.
|
|
|
|
```sh
|
|
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의 `issuer`가
|
|
`https://id.learn.hyeonworks.com/realms/hyeonworks`와 정확히 같은지 확인한다.
|
|
3. 다음 전용 스크립트로 OIDC 목표 상태와 브랜딩을 함께 적용한다.
|
|
|
|
```sh
|
|
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 상태를 적용하거나
|
|
복구하지 않는다.
|