fix(dashboard): address persistent auth review
This commit is contained in:
+26
-44
@@ -1,57 +1,26 @@
|
||||
# k3s 배포
|
||||
|
||||
`base/`는 `jarca-ledger` namespace에 다음 리소스를 배포하는 Kustomize base다.
|
||||
|
||||
- 단일 `Deployment`와 `Recreate` 전략
|
||||
- 동일 ARM64 이미지의 MCP·Dashboard 컨테이너
|
||||
- SQLite 초기화를 담당하는 init container
|
||||
- 공유 `ReadWriteOnce` PVC
|
||||
- Dashboard용 내부 `ClusterIP` Service
|
||||
- Dashboard port `8501` ingress만 허용하는 `NetworkPolicy`
|
||||
- UID/GID `1000:1000`, `fsGroup: 1000`, read-only root filesystem
|
||||
`base/`는 MCP와 Dashboard를 같은 SQLite PVC에 두는 단일 Deployment 구조입니다. Dashboard 인증 secret은 base manifest에 실제 값으로 저장하지 않습니다.
|
||||
|
||||
## 사전 준비
|
||||
|
||||
Base는 실제 Secret 값을 포함하지 않는다. 먼저 namespace를 생성한다.
|
||||
namespace와 승인된 secret manager 절차를 준비합니다.
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/k8s/base/namespace.yaml
|
||||
```
|
||||
|
||||
그 다음 승인된 secret manager나 Git에 기록되지 않는 운영 절차로 다음 Secret을 `jarca-ledger` namespace에 생성한다.
|
||||
`jarca-ledger` namespace의 Dashboard secret에는 다음 key만 둡니다.
|
||||
|
||||
- `jarca-ledger-registry`
|
||||
- type: `kubernetes.io/dockerconfigjson`
|
||||
- `gitea.jsfamily.quest`에서 image를 pull할 수 있는 credential
|
||||
- `jarca-ledger-dashboard-auth`
|
||||
- key `admin-id`
|
||||
- key `admin-password`
|
||||
- key `signing-secret` (32 byte 이상의 독립적인 무작위 값)
|
||||
- `admin-id`
|
||||
- `admin-password`
|
||||
- `signing-secret` (UTF-8 32 byte 이상의 독립 random value)
|
||||
|
||||
Registry credential을 application container에 환경변수나 volume으로 전달하지 않는다. `imagePullSecrets`로만 사용한다.
|
||||
|
||||
## Immutable image 지정
|
||||
|
||||
`base/deployment.yaml`의 release version tag는 템플릿 기본값일 뿐이며 운영 배포에 안전한 immutable 참조가 아니다. Release commit 자체에는 최종 image digest를 넣을 수 없다. Image의 revision label이 release commit을 가리키므로, 그 commit에 digest를 기록하면 commit과 image digest가 다시 바뀌는 자기참조가 발생한다.
|
||||
|
||||
Release가 완료된 뒤 검증된 digest를 사용하는 환경/인프라 저장소의 overlay를 생성한다. 예를 들어 `environments/production/jarca-ledger/kustomization.yaml`은 다음과 같다.
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../../../jarca-ledger-mcp/deploy/k8s/base
|
||||
images:
|
||||
- name: gitea.jsfamily.quest/jsfamily/jarca-ledger-mcp
|
||||
newName: gitea.jsfamily.quest/jsfamily/jarca-ledger-mcp
|
||||
digest: sha256:<verified-release-digest>
|
||||
```
|
||||
|
||||
두 application container와 init container는 반드시 같은 digest를 사용해야 한다.
|
||||
Registry credential은 `imagePullSecrets`로만 사용합니다. 실제 secret 값, token, cookie를 Git, manifest, command history 또는 문서에 기록하지 마세요.
|
||||
|
||||
## 배포
|
||||
|
||||
Namespace와 Secret 준비가 끝난 뒤, base가 아니라 위의 외부 digest-pinned overlay를 적용한다.
|
||||
검증된 release digest를 외부 environment overlay에서 고정하고 적용합니다.
|
||||
|
||||
```bash
|
||||
kubectl apply -k environments/production/jarca-ledger
|
||||
@@ -59,11 +28,24 @@ kubectl -n jarca-ledger rollout status deployment/jarca-ledger
|
||||
kubectl -n jarca-ledger get pods,svc,pvc
|
||||
```
|
||||
|
||||
Dashboard를 외부에 공개하는 Ingress나 NodePort는 environment overlay에서 추가한다. 인증 cookie가 항상 `Secure`이고 endpoint가 ASGI HTTPS scheme을 요구하므로 공개 origin에는 TLS가 필수다. Base의 `FORWARDED_ALLOW_IPS=127.0.0.1`은 ingress forwarded header를 신뢰하지 않는 fail-closed 기본값이다. TLS를 ingress에서 종료하는 overlay는 이 값을 실제 ingress-controller source IP/CIDR allowlist로 교체해야 하며 `*` 또는 불특정 network를 사용해서는 안 된다. TLS 종료 계층은 `Host`와 `Origin`을 보존하고 `X-Forwarded-Proto: https`를 설정해야 한다. 인증 cookie의 발급·검증 자체는 Dashboard 프로세스가 수행한다. Base에는 MCP Service가 없고, `NetworkPolicy`는 workload ingress를 dashboard port `8501`로 제한하여 인증 계층이 없는 MCP port `8000` ingress를 차단한다. Cluster의 network plugin이 `NetworkPolicy`를 enforce하는지 운영 환경에서 확인한다.
|
||||
MCP와 Dashboard container 및 init container는 동일 digest를 사용해야 합니다. SQLite를 공유하므로 replicas는 `1`, update 전략은 `Recreate`입니다.
|
||||
|
||||
## Dashboard 인증 운영
|
||||
|
||||
Frontend container에만 다음 환경변수를 Kubernetes Secret 또는 동등한 secret store에서 주입합니다.
|
||||
|
||||
- `JARCA_DASHBOARD_ADMIN_ID`
|
||||
- `JARCA_DASHBOARD_ADMIN_PASSWORD`
|
||||
- `JARCA_DASHBOARD_SIGNING_SECRET`
|
||||
|
||||
환경변수가 누락·빈 값·read error·짧은 secret이면 Dashboard는 fail-closed합니다. token subject는 고정 비민감 role `dashboard-admin`이며 server-only configured credential binding을 사용합니다. DB-backed session/token registry와 per-token revocation은 없습니다. Password, signing secret 또는 admin ID rotation은 기존 token을 전체 무효화합니다.
|
||||
|
||||
공개 Dashboard origin에는 TLS가 필수입니다. Ingress 또는 reverse proxy는 `Host`, `Origin`, `Referer`, `X-Forwarded-Proto: https`를 보존해야 하며 `FORWARDED_ALLOW_IPS`는 실제 ingress-controller source IP/CIDR로 제한합니다. `*` 또는 불특정 network를 사용하지 마세요. Login과 `/api/auth/logout`은 same-origin HTTPS POST이고 `/api/auth/cleanup` GET은 same-origin HTTPS `Referer`를 검증합니다. Invalid cookie는 cleanup route의 matching `Max-Age=0` deletion 뒤 unauthenticated page로 이동하고, explicit logout도 같은 deletion을 수행합니다. 인증 실패 지연은 rate limiting을 대체하지 않으므로 TLS ingress 경계에 별도 rate limit을 적용합니다.
|
||||
|
||||
Base는 인증 계층이 없는 MCP port `8000`을 Service로 공개하지 않고 NetworkPolicy로 ingress를 제한합니다. NetworkPolicy enforcement는 운영 cluster에서 확인해야 합니다.
|
||||
|
||||
## SQLite와 PVC
|
||||
|
||||
- PVC 전체를 `/data`에 mount한다. SQLite journal, WAL, SHM 파일이 DB 옆에 생성되므로 DB 파일 하나만 `subPath`로 mount하지 않는다.
|
||||
- `replicas`는 `1`, update 전략은 `Recreate`로 유지한다. RollingUpdate는 일시적으로 두 Pod가 같은 SQLite DB를 열 수 있다.
|
||||
- k3s `local-path` volume에서는 `fsGroup: 1000` 적용 여부를 기존 PVC에서도 확인한다. 권한이 맞지 않으면 rollout 전에 운영 절차로 소유권을 수정하고, 상시 privileged `chown` init container는 두지 않는다.
|
||||
- 같은 PVC 안의 backup은 node 또는 disk 손실에 대한 재해 복구가 아니다. 별도 저장소로 backup을 내보낸다.
|
||||
- PVC 전체를 `/data`에 mount하여 SQLite journal/WAL/SHM을 함께 보존합니다.
|
||||
- `replicas: 1`과 `Recreate`를 유지하여 두 Pod가 같은 SQLite를 동시에 열지 않게 합니다.
|
||||
- 별도 저장소로 backup을 내보내며 PVC만 재해 복구로 간주하지 않습니다.
|
||||
|
||||
Reference in New Issue
Block a user