init: 아르고cd, vault 기반 인프라 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:31:09 +09:00
parent 8879d0a3fe
commit d507ac6ee9
137 changed files with 11431 additions and 1 deletions
+97
View File
@@ -0,0 +1,97 @@
# Agent Guide
이 파일은 이 저장소에서 작업을 시작하는 사람과 에이전트를 위한 첫 진입 문서입니다.
## Read First
작업을 시작하면 아래 순서로 문서를 읽습니다.
1. 이 파일 `AGENT.md`
2. 루트 [README.md](/home/donghyeon/dev/Project-Auth-GitOps/README.md)
3. 현재 작업과 직접 관련된 runbook
- Vault transit: [runbooks/vault-transit/dev/README.md](/home/donghyeon/dev/Project-Auth-GitOps/runbooks/vault-transit/dev/README.md)
- Workload Vault: [runbooks/vault/dev/README.md](/home/donghyeon/dev/Project-Auth-GitOps/runbooks/vault/dev/README.md)
- Argo CD 구조: [argocd/README.md](/home/donghyeon/dev/Project-Auth-GitOps/argocd/README.md)
## Working Rules
- README 최상단 `현재 최신 Dev 아키텍처` 는 최신 상태로 유지합니다.
- 기존 cycle은 지우지 말고 README 맨 아래에 새 cycle을 추가합니다.
- ops 변경이나 장애 대응을 했으면 명령, 관찰, 판단 근거, 수정, 검증을 함께 남깁니다.
- 워크플로 구조를 크게 바꿨으면 README에 새 cycle을 추가합니다.
- 민감한 값은 절대 문서에 기록하지 않습니다.
- token, password, kubeconfig 본문, secret payload 금지
- 대신 존재 여부, 길이, secret name, 리소스 상태만 기록
- repo 밖 임시 파일(`/tmp/...`)로 작업한 secret manifest는 Git에 넣지 않습니다.
- Vault 관련 자동화는 **bootstrap****reconcile** 을 분리합니다.
- bootstrap: privileged token 필요, 수동 runbook
- reconcile: workflow AppRole 기반 routine CI
- GitHub Actions YAML에는 긴 Bash를 직접 넣지 않고 `scripts/ci/*.sh` wrapper로 분리합니다.
- GitHub context(`inputs`, `client_payload`) 는 inline Bash에서 직접 쓰지 않고 step `env:` 로 매핑한 뒤 스크립트에서 읽습니다.
- `scripts/ci/reconcile-vault-dev.sh` 는 기본적으로 port-forward 모드지만, 장기적으로는 `RECONCILE_USE_PORT_FORWARD=false` + in-cluster service URL로 돌리는 방향이 목표입니다.
- `vault-dev-reconcile.yaml` 은 아래 GitHub Variables 로 runner/네트워크 전환을 제어합니다.
- `VAULT_DEV_RECONCILE_RUNS_ON`
- `RECONCILE_USE_PORT_FORWARD`
- `TRANSIT_VAULT_ADDR`
- `WORKLOAD_VAULT_ADDR`
## Troubleshooting Format
README cycle의 `### 6. 트러블슈팅 메모` 에 아래 형식으로 남깁니다.
- 재현/확인 명령: 실제로 사용한 명령
- 핵심 관찰값: 에러 문구, 상태 변화, 이벤트, 로그 핵심
- 판단 근거: 왜 그 관찰값을 보고 해당 원인이라고 판단했는지
- 수정 또는 조치: 어떤 파일/리소스를 바꿨는지
- 검증 명령: 해결 여부를 다시 확인한 명령
## Current Dev Pitfalls
- Argo CD `Application``Repository not found` 를 내면 로컬 git 인증이 아니라 `argocd` namespace의 repo credential secret부터 확인합니다.
- self-hosted runner 문제는 workflow YAML보다 먼저 runner 호스트의 실제 CLI 설치 여부를 확인합니다.
- `command -v kubectl vault jq base64 curl terraform`
- Vault raft 사용 시 `api_addr`, `cluster_addr`, listener `cluster_address`, service/deployment의 `8201` 포트 일관성을 먼저 확인합니다.
- `hashicorp/vault` 이미지를 ConfigMap mount와 함께 쓸 때는 read-only mount 충돌과 단일 PVC rollout 충돌을 같이 봅니다.
- 필요하면 `/tmp` 로 config 복사 후 실행, `strategy: Recreate` 적용
- Vault Agent template에 여러 `export` 줄을 렌더링할 때는 aggressive trim(`{{- ... -}}`) 때문에 줄바꿈이 붙지 않는지 실제 `/vault/secrets/*` 파일을 확인합니다.
- Postgres PVC를 유지하는 상태에서 Vault KV만 바꾸면 DB 내부 사용자 비밀번호와 주입값이 어긋날 수 있습니다. KV 값과 persisted DB state를 함께 확인합니다.
- Headless Service를 DB connection host로 쓸 때는 pod 안에서 실제로 어떤 이름이 해석되는지 먼저 확인합니다.
- `postgres.platform.svc.cluster.local` 이 안 되면 `postgres-0.postgres.platform.svc.cluster.local` 같이 pod FQDN 확인
- CI에서 Terraform local backend를 쓰면 self-hosted runner가 기존 `.terraform-state/*.tfstate` 를 실제로 보고 있는지 먼저 확인합니다. state가 완전히 없을 때뿐 아니라 일부 리소스만 남은 partial state도 위험합니다.
- `terraform state list`
- `terraform state show <resource>`
- workflow에서 빠진 리소스를 개별 import 하도록 유지
- 단, `vault_approle_auth_backend_role_secret_id` 처럼 provider가 import를 지원하지 않는 리소스는 예외로 두고 재생성으로 수렴시킵니다.
- Vault KV-v2 mount는 provider import 결과가 `type=kv` + `options.version=2` 로 보이므로, 선언도 그 형태로 맞추고 `prevent_destroy = true` 를 유지합니다.
- seal token을 state로 다시 맞출 때는 `auth/token/revoke-accessor` 권한이 필요합니다.
- `vault_mount`, `vault_auth_backend`, `vault_token.seal` 같이 bootstrap 성격이 강한 리소스는 routine CI에서 replacement가 나지 않게 drift를 최소화합니다.
- 필요하면 `ignore_changes`
- 더 좋게는 bootstrap 단계와 reconcile 단계를 분리
- `kubectl rollout status` 실패 시 `describe`, `logs`, `events` 를 세트로 봅니다.
## Default Command Order
Argo CD app 문제:
```bash
kubectl -n argocd get applications
kubectl -n argocd describe application <name>
kubectl -n argocd get secrets
```
Runner 문제:
```bash
command -v kubectl
command -v vault
command -v terraform
```
Vault pod 문제:
```bash
kubectl -n <ns> get deploy,pods
kubectl -n <ns> describe deployment <name>
kubectl -n <ns> logs deploy/<name> --tail=200
kubectl -n <ns> get events --sort-by=.lastTimestamp | tail -n 30
```
+3044
View File
File diff suppressed because it is too large Load Diff
+1600 -1
View File
File diff suppressed because it is too large Load Diff
+69
View File
@@ -0,0 +1,69 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 1
selector:
matchLabels:
app: api-server
template:
metadata:
labels:
app: api-server
spec:
serviceAccountName: api-server
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api-server
image: ghcr.io/donghyeonka/project-api-server
imagePullPolicy: Always
ports:
- containerPort: 8082
name: http
envFrom:
- configMapRef:
name: api-server-config
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1000m
memory: 512Mi
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 15
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 20
timeoutSeconds: 3
failureThreshold: 5
periodSeconds: 15
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 5
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 12
+7
View File
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- serviceaccount.yaml
- service.yaml
- deployment.yaml
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: api-server
spec:
selector:
app: api-server
ports:
- name: http
port: 80
targetPort: 8082
type: ClusterIP
+7
View File
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-server
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-regcred
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: api-server-config
data:
APP_SERVER_PORT: "8082"
APP_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: http://auth-public.auth-dev.svc.cluster.local
@@ -0,0 +1,14 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: ghcr-regcred
namespace: api-dev
spec:
encryptedData:
.dockerconfigjson: AgC7O6nq0aej7pxKXYyJy01HomK4s4jqkrwJ2iiVqjWrv4LjMqYY/8XiPnv7eyOAhBlpnSjULZ0pNPnQjUh6TUkcfbV0VOXjha6qkFIRZXLTlySvooS4FJH3lGjYBlli5bDfVw2KZTy9zR3qcpclOVJo5eEGJ+3rrl6NCUV7CzXfhpMsdATXtDRK1reSlpS2qWgLYJ8Dt0mdaz9yt1baGC3PwjeohnbmeCxRDi8HWOgWTXj0NqBtnpHnzQz5dox5NduQQCDh4ZrP7XK4CgeC3OIuTy1BokpTD5xfN3e0apiiZcUKQCN2I4NJ+B/JqgxceLxVXl3kYswlH+MjkzuejB3OPcGJsfQAs3gJUPB460mzT2S21xm4GloqL73utz+JuyE4pmCuPbFzgaJFNEamtAWtwZRiUwpjBG0HHLg2JPuLc07ljzfSiO9IqOA2zFmgYTX1dLdhrgSzIO6jaWFvctKTqavPxTRHNjBG9as+9gbQ7caH7vcV4k7q7XyEWpeEv9rCyVWcuRvZ7QNkHU1ss44uimr5I43JkEnsmacFRanxIlB80t357iGNiGNGCJFgfiVnh1PqmPlCvEtA42iTt+++2aU+x1SmiS4LYEs4YowVmanm0t1dBWSOMC7y6kM91W/fGPEo5i0lWbdvDKFuNS8RAWu4mXTgLjgbWz9a5PMcdioh0JMxV78FkDkU1jte/hFeIMOmA/xwrzIXVu7JTJK5a+6A9Dmhjpuiunv1nhDsxg1okPjiJ7W4T2pc0JpU+7XjhsXipE0FCwiNxwsh0GvI5MFGmipQwKbcSJuQJrXqAbTZZyDpa+0rUyc9YhYxAgxKf8AzxffMgDKm68MuS8KVdvIhC0dKUOccRnmrSYN8GMJSlZE065MOjY5FGsVfPRPHMYLhQnkNQPXSDB34UXl97mrUIz6mR3mnZar0PKqm9d5vZ19ujIwV7ZQEar8Sf6ZbT8n25hLUmq6JkGzsRiHo42S2JoVl1QlHgzQbv6i/JPsjzQcKfVhtAIXiyMIMvsbYLCjX9df6yVU3e0pCBVl3sGSMbOf4/QVrJtCqog==
template:
metadata:
name: ghcr-regcred
namespace: api-dev
type: kubernetes.io/dockerconfigjson
+19
View File
@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-server
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
ingressClassName: traefik
rules:
- host: api-public.api-dev.svc.cluster.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-server
port:
number: 80
@@ -0,0 +1,21 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: api-dev
resources:
- ../../base
- namespace.yaml
- configmap.yaml
- ingress.yaml
- public-access.yaml
- networkpolicy.yaml
- ghcr-regcred.sealedsecret.yaml
generatorOptions:
disableNameSuffixHash: true
images:
- name: ghcr.io/donghyeonka/project-api-server
newName: ghcr.io/donghyeonka/project-api-server
newTag: fd097c9
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
name: api-dev
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
@@ -0,0 +1,75 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-dev-default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-dev-allow-dns-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-dev-allow-public-egress
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8000
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-dev-allow-ingress-from-traefik
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8082
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: api-public
spec:
type: ExternalName
externalName: traefik.kube-system.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80
@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: api-prod
resources:
- ../../base
- namespace.yaml
images:
- name: ghcr.io/donghyeonka/project-api-server
newName: ghcr.io/donghyeonka/project-api-server
newTag: fd097c9
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
name: api-prod
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
@@ -0,0 +1,10 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: auth-db-migration
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/sync-wave: "-2"
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-regcred
@@ -0,0 +1,69 @@
apiVersion: batch/v1
kind: Job
metadata:
name: auth-db-migration
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
argocd.argoproj.io/sync-wave: "-1"
spec:
backoffLimit: 1
template:
metadata:
labels:
app: auth-db-migration
spec:
serviceAccountName: auth-db-migration
automountServiceAccountToken: false
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: auth-db-migration
image: ghcr.io/donghyeonka/project-auth-server
imagePullPolicy: IfNotPresent
command:
- java
- -jar
- /app/migration.jar
env:
- name: SPRING_MAIN_WEB_APPLICATION_TYPE
value: none
- name: APP_PERSISTENCE_MIGRATION_RUN_ON_STARTUP
value: "true"
- name: APP_PERSISTENCE_MIGRATION_LOCATION
value: classpath:db/migration
- name: APP_DATASOURCE_URL
valueFrom:
configMapKeyRef:
name: auth-server-config
key: APP_DATASOURCE_URL
- name: APP_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: postgres-auth-server-credentials
key: APP_DATASOURCE_USERNAME
- name: APP_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-auth-server-credentials
key: APP_DATASOURCE_PASSWORD
- name: APP_DATASOURCE_DRIVER_CLASS_NAME
value: org.postgresql.Driver
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
+78
View File
@@ -0,0 +1,78 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-server
annotations:
argocd.argoproj.io/sync-wave: "0"
spec:
replicas: 1
selector:
matchLabels:
app: auth-server
template:
metadata:
labels:
app: auth-server
spec:
serviceAccountName: auth-server
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: auth-server
image: ghcr.io/donghyeonka/project-auth-server
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
name: http
envFrom:
- configMapRef:
name: auth-server-config
- secretRef:
name: postgres-auth-server-credentials
- secretRef:
name: keycloak-client-auth-server
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: false
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1024Mi
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
initialDelaySeconds: 20
timeoutSeconds: 3
failureThreshold: 3
periodSeconds: 10
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 30
timeoutSeconds: 3
failureThreshold: 5
periodSeconds: 15
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
initialDelaySeconds: 10
timeoutSeconds: 3
periodSeconds: 10
failureThreshold: 12
+9
View File
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- serviceaccount.yaml
- auth-db-migration-serviceaccount.yaml
- service.yaml
- db-migration-job.yaml
- deployment.yaml
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: auth-server
spec:
selector:
app: auth-server
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
@@ -0,0 +1,7 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: auth-server
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-regcred
@@ -0,0 +1,26 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: auth-server-config
data:
SPRING_PROFILES_ACTIVE: dev
SERVER_FORWARD_HEADERS_STRATEGY: framework
APP_DOCS_TITLE: Project Auth Server API
APP_DOCS_DESCRIPTION: dev auth-server OpenAPI
APP_DOCS_VERSION: v1
APP_DATASOURCE_URL: jdbc:postgresql://postgres.platform.svc.cluster.local:5432/project_auth
APP_PERSISTENCE_MIGRATION_RUN_ON_STARTUP: "false"
APP_SECURITY_OAUTH2_KEYCLOAK_ISSUER_URI: http://keycloak-public.platform.svc.cluster.local/realms/project-auth
APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_ID: project-auth-server
APP_SECURITY_OAUTH2_GOOGLE_REGISTRATION_ID: keycloak-google
APP_SECURITY_OAUTH2_GOOGLE_IDP_HINT: google
APP_SECURITY_OAUTH2_GITHUB_REGISTRATION_ID: keycloak-github
APP_SECURITY_OAUTH2_GITHUB_IDP_HINT: github
APP_SECURITY_JWT_ISSUER: http://auth-public.auth-dev.svc.cluster.local
APP_SECURITY_JWT_ACTIVE_KEY_ID: dev-vault-rsa-1
APP_SECURITY_JWT_GENERATE_KEY_PAIR_ON_STARTUP: "false"
APP_SECURITY_JWT_ACCESS_TOKEN_EXPIRATION: PT30M
APP_SECURITY_JWT_VAULT_ENABLED: "true"
APP_SECURITY_JWT_VAULT_ADDRESS: http://vault.vault.svc.cluster.local:8200
APP_SECURITY_JWT_VAULT_MOUNT_PATH: transit
APP_SECURITY_JWT_VAULT_TRANSIT_KEY_NAME: project-auth-jwt
@@ -0,0 +1,45 @@
apiVersion: batch/v1
kind: Job
metadata:
name: auth-db-migration
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-secret-migration-env: database/creds/auth-db-migration-dev
vault.hashicorp.com/agent-inject-template-migration-env: |
{{- with secret "database/creds/auth-db-migration-dev" -}}
export APP_DATASOURCE_USERNAME={{ printf "%q" .Data.username }}
export APP_DATASOURCE_PASSWORD={{ printf "%q" .Data.password }}
{{- end }}
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/agent-run-as-group: "10001"
vault.hashicorp.com/agent-run-as-user: "10001"
vault.hashicorp.com/role: auth-db-migration-dev
spec:
automountServiceAccountToken: true
containers:
- name: auth-db-migration
command:
- /bin/sh
- -ec
args:
- |
. /vault/secrets/migration-env
exec java -jar /app/migration.jar
env:
- $patch: replace
- name: SPRING_MAIN_WEB_APPLICATION_TYPE
value: none
- name: APP_PERSISTENCE_MIGRATION_RUN_ON_STARTUP
value: "true"
- name: APP_PERSISTENCE_MIGRATION_LOCATION
value: classpath:db/migration
- name: APP_DATASOURCE_URL
valueFrom:
configMapKeyRef:
name: auth-server-config
key: APP_DATASOURCE_URL
- name: APP_DATASOURCE_DRIVER_CLASS_NAME
value: org.postgresql.Driver
@@ -0,0 +1,39 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth-server
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-cache-enable: "true"
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-secret-runtime-env: kv/data/dev/platform/postgres/auth-server
vault.hashicorp.com/agent-inject-template-runtime-env: |
{{ with secret "kv/data/dev/platform/postgres/auth-server" }}
export APP_DATASOURCE_USERNAME={{ printf "%q" .Data.data.APP_DATASOURCE_USERNAME }}
export APP_DATASOURCE_PASSWORD={{ printf "%q" .Data.data.APP_DATASOURCE_PASSWORD }}
{{ end }}
{{ with secret "kv/data/dev/platform/keycloak/client-auth-server" }}
export APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_SECRET={{ printf "%q" .Data.data.APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_SECRET }}
{{ end }}
vault.hashicorp.com/agent-inject-token: "true"
vault.hashicorp.com/agent-run-as-group: "10001"
vault.hashicorp.com/agent-run-as-user: "10001"
vault.hashicorp.com/role: auth-server-dev
spec:
automountServiceAccountToken: true
containers:
- name: auth-server
command:
- /bin/sh
- -ec
args:
- |
. /vault/secrets/runtime-env
export APP_SECURITY_JWT_VAULT_TOKEN="$(cat /vault/secrets/token)"
exec java -jar /app/application.jar
envFrom:
- configMapRef:
name: auth-server-config
@@ -0,0 +1,14 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: ghcr-regcred
namespace: auth-dev
spec:
encryptedData:
.dockerconfigjson: AgAIKHR/abtMIrkphJPG63jmOCXIMSBdDMdMUYjx37FRV9SpCA/j3UTHhhZlDSK5gXK/p9FGsR1mBjME+imHEa7/rqBMzfSoJjRQCln3TjD1GT+XJ9J8/qS9AQl+GG9kcJfVMTfGslFbQc1993uURdb/CDupe8XsJosMfzvV5h+SpSdytU2Y8CrhZSDNtz7/2Y+x0LQNfyafjXCxsc1kS5qt3WzPFD/zyk70+e2oq868ETBaij2dHEAQXH+XVz2ilqyzLE633X3/Wf/ADRMJ1i7XWaMir01mSXaB97U2+OPaSRJDf1TAfikn6D4BydNsTgI2UQ839edrgr9EXz1Sr4oYdpWjOpNmtH6OjfiiRj2r0LMvNKR/C1ruROfBcEV7y3kXpKRM5kwG+WM1/1B0ziodu0LZJl23QAgfNnTJrYlJJXJNSFNapPMWEcq91TMeauSIyRgbtLUZEemmlTVybt2SV86Yr+w9SlNAsBJGQaW34Ljfmu7X9H0JwTDkphWUotSPmmKj2GFdpVaNwL0Xvi6OoytiWu0rJWVkGar3mnyLPrkVLb5AlOZYPKxfPhZJfwZxPXBqgWj+6kHGs1SpH6v55wVIz1MdMoGcVqcjlOiMNFd2leOyejYQvRBXKRW3wgzgB6J0OdL1S8z1cOfblO4egx9C6RsaxDC//VDK36aAYvdc55Hsk2GC4iZKsug6EAdXWiiJjdM9yfAcGcv7LemQneA3OslJTdDTpWUdbREiIDXBX4PjEQz0G4oI5YDNYvS8NxUOWZ1ly0BH+uHbcm69tnD9ATSVvjvtKlLiP7/BCYZAguCzNEYkmf1neTtHYlVsoPmIuTkifaf9qRPU831MYj8wTfz56YVT1+aSbfQBK4XP49+bIf5AVo9K5JuEQETqMhZ0G05VjmlwUrgtQ60oZLu8dbvFyt/0VxfWBGcCi+AW+nmro3c14XmA4k1d09xtuoa+8QEObtOTEow3+tyqL46FlJgl7UjX8dd4p7gUAF/iADw8a0M32kIN0NHJ9o2EsFe85rn9qBoDLGrogDXRXJ4iCxE0Zgf7bw6tFA==
template:
metadata:
name: ghcr-regcred
namespace: auth-dev
type: kubernetes.io/dockerconfigjson
@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-server
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
ingressClassName: traefik
rules:
- host: auth-public.auth-dev.svc.cluster.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: auth-server
port:
number: 80
@@ -0,0 +1,25 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: auth-dev
resources:
- ../../base
- namespace.yaml
- configmap.yaml
- ingress.yaml
- public-access.yaml
- networkpolicy.yaml
- ghcr-regcred.sealedsecret.yaml
patches:
- path: deployment.vault-patch.yaml
- path: db-migration-job.vault-patch.yaml
generatorOptions:
disableNameSuffixHash: true
images:
- name: ghcr.io/donghyeonka/project-auth-server
newName: ghcr.io/donghyeonka/project-auth-server
newTag: 1f47f2c
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Namespace
metadata:
name: auth-dev
labels:
vault-injection: enabled
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
@@ -0,0 +1,105 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: auth-dev-default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: auth-dev-allow-dns-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: auth-dev-allow-platform-and-vault-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: platform
podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: vault
podSelector:
matchLabels:
app: vault
ports:
- protocol: TCP
port: 8200
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: auth-dev-allow-public-egress
spec:
podSelector:
matchLabels:
app: auth-server
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8000
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: auth-dev-allow-ingress-from-traefik
spec:
podSelector:
matchLabels:
app: auth-server
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8080
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: auth-public
spec:
type: ExternalName
externalName: traefik.kube-system.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80
@@ -0,0 +1,13 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: auth-prod
resources:
- ../../base
- namespace.yaml
images:
- name: ghcr.io/donghyeonka/project-auth-server
newName: ghcr.io/donghyeonka/project-auth-server
newTag: 5648fd2
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
name: auth-prod
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
+18
View File
@@ -0,0 +1,18 @@
## Argo CD Structure
`argocd/` 디렉터리는 환경(`dev`, `prod`)과 성격(`apps`, `infra`) 기준으로 나눠 관리합니다.
- `applications/<env>/apps`: 서비스 애플리케이션 선언
- `applications/<env>/infra`: 공용 인프라/컨트롤러 선언
- `projects/<env>/apps-project.yaml`: 서비스 애플리케이션용 AppProject
- `projects/<env>/infra-project.yaml`: 공용 인프라용 AppProject
현재 `dev`에는 실제 선언을 두고, `prod`는 이후 운영 확장을 위한 구조와 프로젝트 골격을 먼저 유지합니다.
현재 dev `infra`에는 대표적으로 아래 Application이 포함됩니다.
- `vault-transit`: workload Vault transit auto-unseal provider
- `vault`: workload Vault
- `platform`: postgres, keycloak
- `vault-agent-injector`: workload secret injection
- `sealed-secrets`: image pull secret 같은 예외 secret 처리
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: api-server-dev
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "40"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: apps-dev
source:
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
targetRevision: main
path: apps/api-server/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: api-dev
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 5
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: auth-server-dev
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "30"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: apps-dev
source:
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
targetRevision: main
path: apps/auth-server/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: auth-dev
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 5
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: platform-dev
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "20"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infra-dev
source:
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
targetRevision: main
path: infra/platform/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: platform
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 5
@@ -0,0 +1,35 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: sealed-secrets-dev
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infra-dev
source:
repoURL: https://bitnami-labs.github.io/sealed-secrets
chart: sealed-secrets
targetRevision: 2.17.9
helm:
values: |
fullnameOverride: sealed-secrets-controller
destination:
server: https://kubernetes.default.svc
namespace: kube-system
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 5
@@ -0,0 +1,59 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: vault-agent-injector-dev
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "10"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infra-dev
source:
repoURL: https://helm.releases.hashicorp.com
chart: vault
targetRevision: 0.32.0
helm:
values: |
global:
externalVaultAddr: http://vault.vault.svc.cluster.local:8200
tlsDisable: true
server:
enabled: false
injector:
enabled: true
authPath: auth/kubernetes
webhook:
failurePolicy: Fail
namespaceSelector:
matchLabels:
vault-injection: enabled
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 250m
memory: 256Mi
agentImage:
repository: hashicorp/vault
tag: "1.18"
destination:
server: https://kubernetes.default.svc
namespace: vault
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 5
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: vault-transit-dev
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "10"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infra-dev
source:
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
targetRevision: main
path: infra/vault-transit/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: vault-transit
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 5
+34
View File
@@ -0,0 +1,34 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: vault-dev
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "10"
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infra-dev
source:
repoURL: https://github.com/DongHyeonka/Project-Auth-GitOps
targetRevision: main
path: infra/vault/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: vault
syncPolicy:
automated:
enabled: true
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- PruneLast=true
- ApplyOutOfSyncOnly=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
revisionHistoryLimit: 5
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+50
View File
@@ -0,0 +1,50 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: apps-dev
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
description: Dev application workloads managed by Argo CD
sourceRepos:
- https://github.com/DongHyeonka/Project-Auth-GitOps
destinations:
- namespace: auth-dev
server: https://kubernetes.default.svc
- namespace: api-dev
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: ""
kind: PersistentVolumeClaim
- group: "bitnami.com"
kind: SealedSecret
- group: "apps"
kind: Deployment
- group: "apps"
kind: StatefulSet
- group: "apps"
kind: ReplicaSet
- group: "autoscaling"
kind: HorizontalPodAutoscaler
- group: "batch"
kind: Job
- group: "networking.k8s.io"
kind: Ingress
- group: "networking.k8s.io"
kind: NetworkPolicy
- group: "policy"
kind: PodDisruptionBudget
orphanedResources:
warn: true
+68
View File
@@ -0,0 +1,68 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: infra-dev
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
description: Dev shared infrastructure managed by Argo CD
sourceRepos:
- https://github.com/DongHyeonka/Project-Auth-GitOps
- https://bitnami-labs.github.io/sealed-secrets
- https://helm.releases.hashicorp.com
destinations:
- namespace: platform
server: https://kubernetes.default.svc
- namespace: vault
server: https://kubernetes.default.svc
- namespace: vault-transit
server: https://kubernetes.default.svc
- namespace: kube-system
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ""
kind: Namespace
- group: "apiextensions.k8s.io"
kind: CustomResourceDefinition
- group: "rbac.authorization.k8s.io"
kind: ClusterRole
- group: "rbac.authorization.k8s.io"
kind: ClusterRoleBinding
- group: "admissionregistration.k8s.io"
kind: MutatingWebhookConfiguration
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: ""
kind: PersistentVolumeClaim
- group: "bitnami.com"
kind: SealedSecret
- group: "rbac.authorization.k8s.io"
kind: Role
- group: "rbac.authorization.k8s.io"
kind: RoleBinding
- group: "apps"
kind: Deployment
- group: "apps"
kind: StatefulSet
- group: "apps"
kind: ReplicaSet
- group: "autoscaling"
kind: HorizontalPodAutoscaler
- group: "batch"
kind: Job
- group: "networking.k8s.io"
kind: Ingress
- group: "networking.k8s.io"
kind: NetworkPolicy
- group: "policy"
kind: PodDisruptionBudget
orphanedResources:
warn: true
+50
View File
@@ -0,0 +1,50 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: apps-prod
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
description: Prod application workloads managed by Argo CD
sourceRepos:
- https://github.com/DongHyeonka/Project-Auth-GitOps
destinations:
- namespace: auth-prod
server: https://kubernetes.default.svc
- namespace: api-prod
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ""
kind: Namespace
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: ""
kind: PersistentVolumeClaim
- group: "bitnami.com"
kind: SealedSecret
- group: "apps"
kind: Deployment
- group: "apps"
kind: StatefulSet
- group: "apps"
kind: ReplicaSet
- group: "autoscaling"
kind: HorizontalPodAutoscaler
- group: "batch"
kind: Job
- group: "networking.k8s.io"
kind: Ingress
- group: "networking.k8s.io"
kind: NetworkPolicy
- group: "policy"
kind: PodDisruptionBudget
orphanedResources:
warn: true
+61
View File
@@ -0,0 +1,61 @@
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: infra-prod
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
description: Prod shared infrastructure managed by Argo CD
sourceRepos:
- https://github.com/DongHyeonka/Project-Auth-GitOps
- https://bitnami-labs.github.io/sealed-secrets
destinations:
- namespace: platform-prod
server: https://kubernetes.default.svc
- namespace: kube-system
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ""
kind: Namespace
- group: "apiextensions.k8s.io"
kind: CustomResourceDefinition
- group: "rbac.authorization.k8s.io"
kind: ClusterRole
- group: "rbac.authorization.k8s.io"
kind: ClusterRoleBinding
namespaceResourceWhitelist:
- group: ""
kind: ConfigMap
- group: ""
kind: Secret
- group: ""
kind: Service
- group: ""
kind: ServiceAccount
- group: ""
kind: PersistentVolumeClaim
- group: "bitnami.com"
kind: SealedSecret
- group: "rbac.authorization.k8s.io"
kind: Role
- group: "rbac.authorization.k8s.io"
kind: RoleBinding
- group: "apps"
kind: Deployment
- group: "apps"
kind: StatefulSet
- group: "apps"
kind: ReplicaSet
- group: "autoscaling"
kind: HorizontalPodAutoscaler
- group: "batch"
kind: Job
- group: "networking.k8s.io"
kind: Ingress
- group: "networking.k8s.io"
kind: NetworkPolicy
- group: "policy"
kind: PodDisruptionBudget
orphanedResources:
warn: true
@@ -0,0 +1,22 @@
{
"realm": "project-auth",
"enabled": true,
"displayName": "Project Auth",
"sslRequired": "NONE",
"registrationAllowed": false,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"clients": [
{
"clientId": "project-auth-server",
"name": "project-auth-server",
"enabled": true,
"protocol": "openid-connect",
"publicClient": false,
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false
}
]
}
@@ -0,0 +1,10 @@
#!/bin/sh
set -eu
psql -v ON_ERROR_STOP=1 --username "${POSTGRES_USER}" --dbname "${POSTGRES_DB}" <<-EOSQL
CREATE USER ${AUTH_DB_USER} WITH PASSWORD '${AUTH_DB_PASSWORD}';
CREATE DATABASE ${AUTH_DB_NAME} OWNER ${AUTH_DB_USER};
CREATE USER ${KEYCLOAK_DB_USER} WITH PASSWORD '${KEYCLOAK_DB_PASSWORD}';
CREATE DATABASE ${KEYCLOAK_DB_NAME} OWNER ${KEYCLOAK_DB_USER};
EOSQL
@@ -0,0 +1,70 @@
apiVersion: batch/v1
kind: Job
metadata:
name: keycloak-client-sync
annotations:
argocd.argoproj.io/sync-wave: "4"
spec:
backoffLimit: 5
template:
metadata:
labels:
app: keycloak-client-sync
spec:
serviceAccountName: keycloak-client-sync
restartPolicy: OnFailure
containers:
- name: keycloak-client-sync
image: quay.io/keycloak/keycloak:26.5.5
command:
- /bin/sh
- -c
- |
set -eu
until /opt/keycloak/bin/kcadm.sh config credentials \
--server http://keycloak.platform.svc.cluster.local \
--realm master \
--user "$KC_BOOTSTRAP_ADMIN_USERNAME" \
--password "$KC_BOOTSTRAP_ADMIN_PASSWORD" >/dev/null 2>&1; do
sleep 5
done
CLIENT_UUID=$(/opt/keycloak/bin/kcadm.sh get clients \
-r project-auth \
-q clientId="$KEYCLOAK_CLIENT_ID" | sed -n 's/.*"id" : "\([^"]*\)".*/\1/p' | head -n 1)
test -n "$CLIENT_UUID"
/opt/keycloak/bin/kcadm.sh update "clients/${CLIENT_UUID}" \
-r project-auth \
-s "secret=$KEYCLOAK_CLIENT_SECRET" \
-s "baseUrl=$AUTH_SERVER_BASE_URL" \
-s 'redirectUris=["'"$AUTH_SERVER_BASE_URL"'/login/oauth2/code/keycloak-google","'"$AUTH_SERVER_BASE_URL"'/login/oauth2/code/keycloak-github"]' \
-s 'webOrigins=["'"$AUTH_SERVER_BASE_URL"'"]'
env:
- name: KC_BOOTSTRAP_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-bootstrap-admin
key: KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD
- name: KEYCLOAK_CLIENT_ID
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_CLIENT_ID
- name: KEYCLOAK_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: keycloak-client-auth-server
key: KEYCLOAK_CLIENT_SECRET
- name: AUTH_SERVER_BASE_URL
valueFrom:
configMapKeyRef:
name: platform-config
key: AUTH_SERVER_BASE_URL
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: keycloak-client-sync
automountServiceAccountToken: false
@@ -0,0 +1,104 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
annotations:
argocd.argoproj.io/sync-wave: "3"
spec:
replicas: 1
selector:
matchLabels:
app: keycloak
template:
metadata:
labels:
app: keycloak
spec:
serviceAccountName: keycloak
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: keycloak
image: quay.io/keycloak/keycloak:26.5.5
args:
- start-dev
- --import-realm
- --http-port=8080
ports:
- containerPort: 8080
name: http
- containerPort: 9000
name: management
env:
- name: KC_DB
value: postgres
- name: KC_DB_URL
value: jdbc:postgresql://postgres.platform.svc.cluster.local:5432/keycloak
- name: KC_DB_USERNAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_DB_USER
- name: KC_DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-keycloak-credentials
key: KEYCLOAK_DB_PASSWORD
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_BOOTSTRAP_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME
- name: KC_BOOTSTRAP_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: keycloak-bootstrap-admin
key: KEYCLOAK_BOOTSTRAP_ADMIN_PASSWORD
volumeMounts:
- name: keycloak-realm-import
mountPath: /opt/keycloak/data/import/project-auth-realm.json
subPath: project-auth-realm.json
readinessProbe:
httpGet:
path: /health/ready
port: management
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 6
livenessProbe:
httpGet:
path: /health/live
port: management
initialDelaySeconds: 60
periodSeconds: 15
timeoutSeconds: 3
failureThreshold: 6
startupProbe:
httpGet:
path: /health/ready
port: management
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 30
resources:
requests:
cpu: 250m
memory: 768Mi
limits:
cpu: 1000m
memory: 1536Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumes:
- name: keycloak-realm-import
configMap:
name: keycloak-realm-import
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: keycloak
spec:
selector:
app: keycloak
ports:
- name: http
port: 80
targetPort: 8080
type: ClusterIP
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: keycloak
automountServiceAccountToken: false
+22
View File
@@ -0,0 +1,22 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- postgres-serviceaccount.yaml
- keycloak-serviceaccount.yaml
- keycloak-client-sync-serviceaccount.yaml
- postgres-service.yaml
- postgres-statefulset.yaml
- keycloak-service.yaml
- keycloak-deployment.yaml
- keycloak-client-sync-job.yaml
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: postgres-init-script
files:
- files/postgres/01-init-project-auth-databases.sh
- name: keycloak-realm-import
files:
- files/keycloak/project-auth-realm.json
+13
View File
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None
selector:
app: postgres
ports:
- name: postgres
port: 5432
targetPort: 5432
type: ClusterIP
@@ -0,0 +1,5 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: postgres
automountServiceAccountToken: false
@@ -0,0 +1,126 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
annotations:
argocd.argoproj.io/sync-wave: "2"
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
serviceAccountName: postgres
securityContext:
fsGroup: 999
seccompProfile:
type: RuntimeDefault
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: platform-config
key: POSTGRES_SUPERUSER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-superuser-credentials
key: POSTGRES_SUPERUSER_PASSWORD
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: platform-config
key: POSTGRES_DEFAULT_DB
- name: AUTH_DB_NAME
valueFrom:
configMapKeyRef:
name: platform-config
key: AUTH_DB_NAME
- name: AUTH_DB_USER
valueFrom:
configMapKeyRef:
name: platform-config
key: AUTH_DB_USER
- name: AUTH_DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-auth-server-credentials
key: AUTH_DB_PASSWORD
- name: KEYCLOAK_DB_NAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_DB_NAME
- name: KEYCLOAK_DB_USER
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_DB_USER
- name: KEYCLOAK_DB_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-keycloak-credentials
key: KEYCLOAK_DB_PASSWORD
securityContext:
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
- name: postgres-init-script
mountPath: /docker-entrypoint-initdb.d/01-init-project-auth-databases.sh
subPath: 01-init-project-auth-databases.sh
- name: postgres-run
mountPath: /var/run/postgresql
readinessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
livenessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 3
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1024Mi
volumes:
- name: postgres-init-script
configMap:
name: postgres-init-script
defaultMode: 0555
- name: postgres-run
emptyDir: {}
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
@@ -0,0 +1,14 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: platform-config
data:
POSTGRES_SUPERUSER: postgres
POSTGRES_DEFAULT_DB: postgres
AUTH_DB_NAME: project_auth
AUTH_DB_USER: project_auth
KEYCLOAK_DB_NAME: keycloak
KEYCLOAK_DB_USER: keycloak
KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME: admin
KEYCLOAK_CLIENT_ID: project-auth-server
AUTH_SERVER_BASE_URL: http://auth-public.auth-dev.svc.cluster.local
@@ -0,0 +1,70 @@
apiVersion: batch/v1
kind: Job
metadata:
name: keycloak-client-sync
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-perms-keycloak-sync-env: "0644"
vault.hashicorp.com/agent-inject-secret-keycloak-sync-env: kv/data/dev/platform/keycloak/bootstrap-admin
vault.hashicorp.com/agent-inject-template-keycloak-sync-env: |
{{ with secret "kv/data/dev/platform/keycloak/bootstrap-admin" }}
export KC_BOOTSTRAP_ADMIN_PASSWORD={{ printf "%q" .Data.data.KC_BOOTSTRAP_ADMIN_PASSWORD }}
{{ end }}
{{ with secret "kv/data/dev/platform/keycloak/client-auth-server" }}
export KEYCLOAK_CLIENT_SECRET={{ printf "%q" .Data.data.KEYCLOAK_CLIENT_SECRET }}
{{ end }}
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/role: keycloak-client-sync-dev
spec:
automountServiceAccountToken: true
containers:
- name: keycloak-client-sync
command:
- /bin/sh
- -ec
args:
- |
. /vault/secrets/keycloak-sync-env
set -eu
until /opt/keycloak/bin/kcadm.sh config credentials \
--server http://keycloak.platform.svc.cluster.local \
--realm master \
--user "$KC_BOOTSTRAP_ADMIN_USERNAME" \
--password "$KC_BOOTSTRAP_ADMIN_PASSWORD" >/dev/null 2>&1; do
sleep 5
done
CLIENT_UUID=$(/opt/keycloak/bin/kcadm.sh get clients \
-r project-auth \
-q clientId="$KEYCLOAK_CLIENT_ID" | sed -n 's/.*"id" : "\([^"]*\)".*/\1/p' | head -n 1)
test -n "$CLIENT_UUID"
/opt/keycloak/bin/kcadm.sh update "clients/${CLIENT_UUID}" \
-r project-auth \
-s "secret=$KEYCLOAK_CLIENT_SECRET" \
-s "baseUrl=$AUTH_SERVER_BASE_URL" \
-s 'redirectUris=["'"$AUTH_SERVER_BASE_URL"'/login/oauth2/code/keycloak-google","'"$AUTH_SERVER_BASE_URL"'/login/oauth2/code/keycloak-github"]' \
-s 'webOrigins=["'"$AUTH_SERVER_BASE_URL"'"]'
env:
- $patch: replace
- name: KC_BOOTSTRAP_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME
- name: KEYCLOAK_CLIENT_ID
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_CLIENT_ID
- name: AUTH_SERVER_BASE_URL
valueFrom:
configMapKeyRef:
name: platform-config
key: AUTH_SERVER_BASE_URL
@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: keycloak
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
ingressClassName: traefik
rules:
- host: keycloak-public.platform.svc.cluster.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: keycloak
port:
number: 80
@@ -0,0 +1,16 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
spec:
template:
spec:
containers:
- name: keycloak
env:
- name: KC_HOSTNAME
value: keycloak-public.platform.svc.cluster.local
- name: KC_HOSTNAME_STRICT
value: "false"
- name: KC_PROXY_HEADERS
value: xforwarded
@@ -0,0 +1,49 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: keycloak
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-perms-keycloak-env: "0644"
vault.hashicorp.com/agent-inject-secret-keycloak-env: kv/data/dev/platform/postgres/keycloak
vault.hashicorp.com/agent-inject-template-keycloak-env: |
{{ with secret "kv/data/dev/platform/postgres/keycloak" }}
export KC_DB_PASSWORD={{ printf "%q" .Data.data.KEYCLOAK_DB_PASSWORD }}
{{ end }}
{{ with secret "kv/data/dev/platform/keycloak/bootstrap-admin" }}
export KC_BOOTSTRAP_ADMIN_PASSWORD={{ printf "%q" .Data.data.KC_BOOTSTRAP_ADMIN_PASSWORD }}
{{ end }}
vault.hashicorp.com/role: keycloak-dev
spec:
automountServiceAccountToken: true
containers:
- name: keycloak
command:
- /bin/sh
- -ec
args:
- |
. /vault/secrets/keycloak-env
exec /opt/keycloak/bin/kc.sh start-dev --import-realm --http-port=8080
env:
- $patch: replace
- name: KC_DB
value: postgres
- name: KC_DB_URL
value: jdbc:postgresql://postgres.platform.svc.cluster.local:5432/keycloak
- name: KC_DB_USERNAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_DB_USER
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_BOOTSTRAP_ADMIN_USERNAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME
@@ -0,0 +1,18 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: platform
resources:
- ../../base
- namespace.yaml
- configmap.yaml
- keycloak-ingress.yaml
- public-access.yaml
- networkpolicy.yaml
patches:
- path: postgres.vault-patch.yaml
- path: keycloak.vault-patch.yaml
- path: keycloak-client-sync.vault-patch.yaml
- path: keycloak.public-url-patch.yaml
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Namespace
metadata:
name: platform
labels:
vault-injection: enabled
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
@@ -0,0 +1,161 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: platform-default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: platform-allow-dns-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: platform-allow-vault-egress
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: vault
podSelector:
matchLabels:
app: vault
ports:
- protocol: TCP
port: 8200
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: platform-allow-keycloak-egress-to-postgres
spec:
podSelector:
matchLabels:
app: keycloak
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: platform-allow-keycloak-client-sync-to-keycloak
spec:
podSelector:
matchLabels:
app: keycloak-client-sync
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: keycloak
ports:
- protocol: TCP
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: platform-allow-keycloak-ingress
spec:
podSelector:
matchLabels:
app: keycloak
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8080
- from:
- podSelector:
matchLabels:
app: keycloak-client-sync
ports:
- protocol: TCP
port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: platform-allow-postgres-ingress
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: auth-dev
podSelector:
matchExpressions:
- key: app
operator: In
values:
- auth-server
- auth-db-migration
ports:
- protocol: TCP
port: 5432
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: vault
podSelector:
matchLabels:
app: vault
ports:
- protocol: TCP
port: 5432
- from:
- podSelector:
matchLabels:
app: keycloak
ports:
- protocol: TCP
port: 5432
@@ -0,0 +1,69 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
spec:
template:
metadata:
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-inject-perms-postgres-env: "0644"
vault.hashicorp.com/agent-inject-secret-postgres-env: kv/data/dev/platform/postgres/superuser
vault.hashicorp.com/agent-inject-template-postgres-env: |
{{ with secret "kv/data/dev/platform/postgres/superuser" }}
export POSTGRES_PASSWORD={{ printf "%q" .Data.data.POSTGRES_SUPERUSER_PASSWORD }}
{{ end }}
{{ with secret "kv/data/dev/platform/postgres/auth-server" }}
export AUTH_DB_PASSWORD={{ printf "%q" .Data.data.AUTH_DB_PASSWORD }}
{{ end }}
{{ with secret "kv/data/dev/platform/postgres/keycloak" }}
export KEYCLOAK_DB_PASSWORD={{ printf "%q" .Data.data.KEYCLOAK_DB_PASSWORD }}
{{ end }}
vault.hashicorp.com/role: postgres-dev
spec:
automountServiceAccountToken: true
containers:
- name: postgres
command:
- /bin/sh
- -ec
args:
- |
. /vault/secrets/postgres-env
exec docker-entrypoint.sh postgres
env:
- $patch: replace
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
- name: POSTGRES_USER
valueFrom:
configMapKeyRef:
name: platform-config
key: POSTGRES_SUPERUSER
- name: POSTGRES_DB
valueFrom:
configMapKeyRef:
name: platform-config
key: POSTGRES_DEFAULT_DB
- name: AUTH_DB_NAME
valueFrom:
configMapKeyRef:
name: platform-config
key: AUTH_DB_NAME
- name: AUTH_DB_USER
valueFrom:
configMapKeyRef:
name: platform-config
key: AUTH_DB_USER
- name: KEYCLOAK_DB_NAME
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_DB_NAME
- name: KEYCLOAK_DB_USER
valueFrom:
configMapKeyRef:
name: platform-config
key: KEYCLOAK_DB_USER
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Service
metadata:
name: keycloak-public
spec:
type: ExternalName
externalName: traefik.kube-system.svc.cluster.local
ports:
- name: http
port: 80
targetPort: 80
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: platform-config
data:
POSTGRES_SUPERUSER: postgres
POSTGRES_DEFAULT_DB: postgres
AUTH_DB_NAME: project_auth
AUTH_DB_USER: project_auth
KEYCLOAK_DB_NAME: keycloak
KEYCLOAK_DB_USER: keycloak
KEYCLOAK_BOOTSTRAP_ADMIN_USERNAME: admin
KEYCLOAK_CLIENT_ID: project-auth-server
AUTH_SERVER_BASE_URL: http://auth-server.auth-prod.svc.cluster.local
VAULT_TRANSIT_KEY_NAME: project-auth-jwt
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: platform-prod
resources:
- ../../base
- namespace.yaml
- configmap.yaml
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
name: platform-prod
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
+73
View File
@@ -0,0 +1,73 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault-transit
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: vault-transit
template:
metadata:
labels:
app: vault-transit
spec:
containers:
- name: vault-transit
image: hashicorp/vault:1.18
command:
- /bin/sh
- -ec
env:
- name: VAULT_ADDR
value: http://127.0.0.1:8200
args:
- |
cp /vault/config/vault.hcl /tmp/vault.hcl
exec vault server -config=/tmp/vault.hcl
ports:
- containerPort: 8200
name: http
- containerPort: 8201
name: cluster
volumeMounts:
- name: vault-transit-config
mountPath: /vault/config
readOnly: true
- name: vault-transit-data
mountPath: /vault/data
readinessProbe:
exec:
command:
- sh
- -c
- vault status -address=http://127.0.0.1:8200 >/dev/null 2>&1; code=$?; [ "$code" -eq 0 ] || [ "$code" -eq 2 ]
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
exec:
command:
- sh
- -c
- vault status -address=http://127.0.0.1:8200 >/dev/null 2>&1; code=$?; [ "$code" -eq 0 ] || [ "$code" -eq 2 ]
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: vault-transit-config
configMap:
name: vault-transit-config
defaultMode: 0555
- name: vault-transit-data
persistentVolumeClaim:
claimName: vault-transit-data
@@ -0,0 +1,15 @@
ui = true
disable_mlock = true
api_addr = "http://vault-transit.vault-transit.svc.cluster.local:8200"
cluster_addr = "http://vault-transit.vault-transit.svc.cluster.local:8201"
listener "tcp" {
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
tls_disable = 1
}
storage "raft" {
path = "/vault/data"
node_id = "vault-transit-dev-0"
}
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- service.yaml
- deployment.yaml
- pvc.yaml
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: vault-transit-config
files:
- files/vault/vault.hcl
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vault-transit-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: vault-transit
spec:
selector:
app: vault-transit
ports:
- name: http
port: 8200
targetPort: 8200
- name: cluster
port: 8201
targetPort: 8201
type: ClusterIP
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: vault-transit
resources:
- ../../base
- namespace.yaml
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
name: vault-transit
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
+23
View File
@@ -0,0 +1,23 @@
ui = true
disable_mlock = true
api_addr = "http://vault.vault.svc.cluster.local:8200"
cluster_addr = "http://vault.vault.svc.cluster.local:8201"
listener "tcp" {
address = "0.0.0.0:8200"
cluster_address = "0.0.0.0:8201"
tls_disable = 1
}
seal "transit" {
address = "http://vault-transit.vault-transit.svc.cluster.local:8200"
disable_renewal = "false"
key_name = "workload-vault-dev-unseal"
mount_path = "transit/"
tls_skip_verify = "true"
}
storage "raft" {
path = "/vault/data"
node_id = "vault-dev-0"
}
+17
View File
@@ -0,0 +1,17 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- vault-serviceaccount.yaml
- vault-auth-delegator.clusterrolebinding.yaml
- vault-pvc.yaml
- vault-service.yaml
- vault-deployment.yaml
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: vault-config
files:
- files/vault/vault.hcl
@@ -0,0 +1,12 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: vault-server-auth-delegator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:auth-delegator
subjects:
- kind: ServiceAccount
name: vault-server
namespace: vault
+79
View File
@@ -0,0 +1,79 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: vault
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: vault
template:
metadata:
labels:
app: vault
spec:
serviceAccountName: vault-server
containers:
- name: vault
image: hashicorp/vault:1.18
command:
- /bin/sh
- -ec
env:
- name: VAULT_ADDR
value: http://127.0.0.1:8200
- name: VAULT_TOKEN
valueFrom:
secretKeyRef:
name: vault-transit-seal
key: VAULT_TRANSIT_SEAL_TOKEN
args:
- |
cp /vault/config/vault.hcl /tmp/vault.hcl
exec vault server -config=/tmp/vault.hcl
ports:
- containerPort: 8200
name: http
- containerPort: 8201
name: cluster
volumeMounts:
- name: vault-config
mountPath: /vault/config
readOnly: true
- name: vault-data
mountPath: /vault/data
readinessProbe:
exec:
command:
- sh
- -c
- vault status -address=http://127.0.0.1:8200 >/dev/null 2>&1; code=$?; [ "$code" -eq 0 ] || [ "$code" -eq 2 ]
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
livenessProbe:
exec:
command:
- sh
- -c
- vault status -address=http://127.0.0.1:8200 >/dev/null 2>&1; code=$?; [ "$code" -eq 0 ] || [ "$code" -eq 2 ]
initialDelaySeconds: 20
periodSeconds: 15
timeoutSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumes:
- name: vault-config
configMap:
name: vault-config
defaultMode: 0555
- name: vault-data
persistentVolumeClaim:
claimName: vault-data
+10
View File
@@ -0,0 +1,10 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: vault-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: vault
spec:
selector:
app: vault
ports:
- name: http
port: 8200
targetPort: 8200
- name: cluster
port: 8201
targetPort: 8201
type: ClusterIP
@@ -0,0 +1,4 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-server
@@ -0,0 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: vault
resources:
- ../../base
- namespace.yaml
- networkpolicy.yaml
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
name: vault
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
+140
View File
@@ -0,0 +1,140 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vault-server-default-deny
spec:
podSelector:
matchLabels:
app: vault
policyTypes:
- Ingress
- Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vault-server-allow-dns-egress
spec:
podSelector:
matchLabels:
app: vault
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vault-server-allow-transit-egress
spec:
podSelector:
matchLabels:
app: vault
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: vault-transit
podSelector:
matchLabels:
app: vault-transit
ports:
- protocol: TCP
port: 8200
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vault-server-allow-postgres-egress
spec:
podSelector:
matchLabels:
app: vault
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: platform
podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vault-server-allow-kubernetes-api-egress
spec:
podSelector:
matchLabels:
app: vault
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.43.0.1/32
ports:
- protocol: TCP
port: 443
- to:
- ipBlock:
cidr: 10.208.141.98/32
ports:
- protocol: TCP
port: 6443
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vault-server-allow-ingress-from-workloads
spec:
podSelector:
matchLabels:
app: vault
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: auth-dev
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: platform
ports:
- protocol: TCP
port: 8200
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: vault-agent-injector-webhook-ingress
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: vault-agent-injector
policyTypes:
- Ingress
ingress:
- ports:
- protocol: TCP
port: 8080
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: vault-prod
resources:
- ../../base
- namespace.yaml
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: Namespace
metadata:
name: vault-prod
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: latest
+149
View File
@@ -0,0 +1,149 @@
## Vault Transit Dev Bootstrap
이 문서는 dev 환경의 **unseal provider Vault** 를 Terraform으로 선언적으로 bootstrap 하는 절차를 정리합니다.
이 Vault는 애플리케이션 secret을 직접 저장하지 않고, 업무용 Vault의 transit auto-unseal provider와 workload seed source 역할만 담당합니다.
### 준비물
- `kubectl`
- `vault`
- `jq`
- `terraform`
- dev 클러스터에 접근 가능한 kubeconfig
### 0. 기동 상태 확인
`init / unseal` 전에 먼저 `vault-transit` pod가 실제로 기동 가능한 상태인지 확인합니다.
```bash
kubectl -n vault-transit get deploy,pods,svc,pvc
kubectl -n vault-transit rollout status deploy/vault-transit --timeout=180s
```
정상 기준:
- `deployment/vault-transit``1/1 Ready`
- pod가 `Running`
- `CrashLoopBackOff` 가 아님
기동이 안 되면 아래를 먼저 봅니다.
```bash
kubectl -n vault-transit describe deployment vault-transit
kubectl -n vault-transit logs deploy/vault-transit --tail=200
kubectl -n vault-transit get events --sort-by=.lastTimestamp | tail -n 30
```
최근 dev 기준 대표 원인은 아래였습니다.
- `Cluster address must be set when using raft storage`
원인: raft storage 사용 시 `api_addr`, `cluster_addr`, listener `cluster_address` 가 빠져 있었음
- `Could not chown /vault/config`
원인: ConfigMap mount가 read-only 인데 이미지 entrypoint가 해당 경로를 `chown` 하려 함
현재 base manifest는 위 이슈를 피하기 위해:
- `api_addr`, `cluster_addr`, `cluster_address` 추가
- service/deployment `8201` cluster 포트 추가
- deployment `strategy: Recreate`
- `/vault/config/vault.hcl``/tmp/vault.hcl` 로 복사 후 `vault server` 실행
형태로 정리되어 있습니다.
### 1. 포트 포워딩
```bash
kubectl port-forward -n vault-transit svc/vault-transit 18200:8200
```
### 2. init / unseal
```bash
export VAULT_ADDR=http://127.0.0.1:18200
vault operator init -format=json > .local/vault-transit-dev-init.json
vault operator unseal "$(jq -r '.unseal_keys_b64[0]' .local/vault-transit-dev-init.json)"
export TF_VAR_vault_token="$(jq -r '.root_token' .local/vault-transit-dev-init.json)"
export TF_VAR_vault_addr="$VAULT_ADDR"
```
### 3. Terraform bootstrap
```bash
terraform -chdir=terraform/vault-transit/dev init -input=false
terraform -chdir=terraform/vault-transit/dev apply -input=false -auto-approve
```
이 apply 는 아래를 선언적으로 맞춥니다.
- `kv` / `transit` secrets engine 활성화
- workload Vault auto-unseal key 생성
- `workload-vault-transit-dev`, `vault-transit-admin-dev`, `vault-transit-automation-dev` policy reconcile
- workflow용 `vault-transit-dev-workflow` AppRole reconcile
- `vault/vault-transit-seal` Kubernetes Secret 갱신
필요한 CI credential은 Terraform output 으로 확인합니다.
```bash
terraform -chdir=terraform/vault-transit/dev output workflow_role_id
terraform -chdir=terraform/vault-transit/dev output -raw workflow_secret_id
```
이 두 값은 `VAULT_TRANSIT_DEV_ROLE_ID`, `VAULT_TRANSIT_DEV_SECRET_ID` 로 CI secret store 에 저장합니다.
### 4. 이후 역할
- 업무용 Vault([vault app](/home/donghyeon/dev/Project-Auth-GitOps/argocd/applications/dev/infra/vault.yaml)) 는 `vault-transit-seal` Secret 의 토큰으로 transit auto-unseal 을 수행합니다.
- `vault-transit` provider Vault 자체는 dev/on-prem 전제에서 여전히 **수동 unseal** 입니다.
- 수동 bootstrap 은 `terraform/vault-transit/dev`, `terraform/vault/dev` 를 사용합니다.
- self-hosted runner routine reconcile 은 provider AppRole 로 로그인한 뒤 `terraform/vault-transit/reconcile`, `terraform/vault/reconcile` 를 차례로 `apply` 합니다.
### 5. Workload seed 값 입력
provider Vault는 여전히 workload seed source of truth 이므로, app 비밀값은 한 번 입력해야 합니다.
```bash
export VAULT_ADDR=http://127.0.0.1:18200
export VAULT_TOKEN="$(vault write -field=token auth/approle/login \
role_id="<vault-transit-workflow-role-id>" \
secret_id="<vault-transit-workflow-secret-id>")"
./scripts/vault-transit/dev/populate-workload-seeds.sh
```
이 스크립트는 값을 **프롬프트로 입력받기 때문에 shell history에 실제 secret이 남지 않습니다.**
주의:
- 이 스크립트는 `TF_VAR_transit_vault_token` 이 아니라 **`VAULT_TOKEN`** 을 사용합니다.
- `VAULT_ADDR` 는 provider Vault 포트포워드인 `http://127.0.0.1:18200` 이어야 합니다.
`populate-workload-seeds.example.sh` 는 필요한 key 구조를 보여주는 참고용 예시입니다.
입력 경로는 목적 기준으로 나뉩니다.
- `kv/dev/workload/platform/postgres/superuser`
- `kv/dev/workload/platform/postgres/auth-server`
- `kv/dev/workload/platform/postgres/keycloak`
- `kv/dev/workload/platform/keycloak/bootstrap-admin`
- `kv/dev/workload/platform/keycloak/client-auth-server`
`kv/dev/workload/bootstrap` 의 workload AppRole credential 은 manual bootstrap 단계의 `terraform/vault/dev` 가 자동으로 씁니다. 사람이 따로 넣지 않습니다.
### 6. 운영자 토큰이 필요한 경우
장기 토큰을 Terraform state 에 저장하지 않기 위해 `vault-transit-admin-dev` 토큰은 자동 발급하지 않습니다.
직접 점검이 필요하면 privileged token 으로 아래처럼 짧은 토큰을 발급합니다.
```bash
export VAULT_ADDR=http://127.0.0.1:18200
export VAULT_TOKEN="$TF_VAR_vault_token"
vault token create -orphan -policy=vault-transit-admin-dev -ttl=1h
```
### 주의
- manual bootstrap state 는 [`.terraform-state/vault-transit-dev.tfstate`](/home/donghyeon/dev/Project-Auth-GitOps/.terraform-state/vault-transit-dev.tfstate) 에 저장됩니다.
- routine reconcile state 는 [`.terraform-state/vault-transit-reconcile.tfstate`](/home/donghyeon/dev/Project-Auth-GitOps/.terraform-state/vault-transit-reconcile.tfstate) 에 저장됩니다.
- Vault provider state 에는 민감한 값이 들어가므로 self-hosted runner 와 로컬 작업 디렉터리를 동일하게 보호해야 합니다.
@@ -0,0 +1,39 @@
path "transit/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "sys/internal/ui/mounts/*" {
capabilities = ["read"]
}
path "sys/mounts/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/auth/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/policies/acl/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/approle/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/token/create-orphan" {
capabilities = ["update"]
}
path "auth/token/revoke" {
capabilities = ["update"]
}
path "auth/token/lookup" {
capabilities = ["update"]
}
@@ -0,0 +1,59 @@
path "transit/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/data/dev/workload/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "kv/metadata/dev/workload/*" {
capabilities = ["read", "delete", "list"]
}
path "sys/internal/ui/mounts/*" {
capabilities = ["read"]
}
path "sys/mounts/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/auth/*" {
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "sys/policies/acl/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/approle/*" {
capabilities = ["create", "read", "update", "delete", "list"]
}
path "auth/token/create" {
capabilities = ["update"]
}
path "auth/token/create-orphan" {
capabilities = ["update"]
}
path "auth/token/revoke" {
capabilities = ["update"]
}
path "auth/token/revoke-accessor" {
capabilities = ["update"]
}
path "auth/token/lookup" {
capabilities = ["update"]
}
path "auth/token/lookup-accessor" {
capabilities = ["update"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
@@ -0,0 +1,15 @@
path "transit/encrypt/workload-vault-dev-unseal" {
capabilities = ["update"]
}
path "transit/decrypt/workload-vault-dev-unseal" {
capabilities = ["update"]
}
path "transit/rewrap/workload-vault-dev-unseal" {
capabilities = ["update"]
}
path "transit/keys/workload-vault-dev-unseal" {
capabilities = ["read"]
}
@@ -0,0 +1,21 @@
#!/usr/bin/env sh
set -eu
vault kv put kv/dev/workload/platform/postgres/superuser \
POSTGRES_SUPERUSER_PASSWORD=change-me
vault kv put kv/dev/workload/platform/postgres/auth-server \
APP_DATASOURCE_USERNAME=project_auth \
APP_DATASOURCE_PASSWORD=change-me \
AUTH_DB_PASSWORD=change-me
vault kv put kv/dev/workload/platform/postgres/keycloak \
KEYCLOAK_DB_PASSWORD=change-me
vault kv put kv/dev/workload/platform/keycloak/bootstrap-admin \
KC_BOOTSTRAP_ADMIN_PASSWORD=change-me
vault kv put kv/dev/workload/platform/keycloak/client-auth-server \
APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_SECRET=change-me \
KEYCLOAK_CLIENT_SECRET=change-me
+174
View File
@@ -0,0 +1,174 @@
## Vault Dev Bootstrap
이 문서는 dev 환경의 **workload Vault** 를 Terraform으로 선언적으로 bootstrap / reconcile 하는 절차를 정리합니다.
이제 책임은 두 단계로 분리합니다.
- bootstrap
- privileged workload token 필요
- 수동 runbook 사용
- reconcile
- bootstrap 완료 후 `vault-dev-reconcile` workflow 가 routine apply 수행
사전 조건:
- [vault-transit bootstrap runbook](/home/donghyeon/dev/Project-Auth-GitOps/runbooks/vault-transit/dev/README.md) 을 먼저 완료해야 합니다.
- `vault` namespace에 `vault-transit-seal` Secret 이 준비되어 있어야 workload Vault 가 auto-unseal 됩니다.
현재 저장소의 source of truth 는 아래 두 Terraform 루트로 분리합니다.
- bootstrap/manual: `terraform/vault/dev`
- reconcile/CI: `terraform/vault/reconcile`
`bootstrap-runbook.sh` 는 bootstrap 루트를, routine workflow 는 reconcile 루트를 호출합니다.
### 준비물
- `kubectl`
- `vault`
- `jq`
- `terraform`
- dev 클러스터에 접근 가능한 kubeconfig
### 1. Vault 포트 포워딩
```bash
kubectl port-forward -n vault svc/vault 8200:8200
```
### 2. Vault init / bootstrap token 준비
```bash
export VAULT_ADDR=http://127.0.0.1:8200
vault operator init -format=json > .local/vault-dev-init.json
export TF_VAR_workload_vault_addr="$VAULT_ADDR"
export TF_VAR_workload_vault_token="$(jq -r '.root_token' .local/vault-dev-init.json)"
```
Transit auto-unseal 구조이므로 정상 상태에서는 `vault operator unseal` 을 반복하지 않습니다.
### 3. Provider seed credential 연결
workload Terraform 은 provider Vault 에서 seed 값을 읽고, 생성한 workload workflow AppRole credential 을 다시 provider Vault bootstrap path 로 써넣습니다.
```bash
export TF_VAR_transit_vault_addr=http://127.0.0.1:18200
export TF_VAR_transit_vault_token="$(VAULT_ADDR="$TF_VAR_transit_vault_addr" vault write -field=token auth/approle/login \
role_id="<vault-transit-workflow-role-id>" \
secret_id="<vault-transit-workflow-secret-id>")"
```
### 4. Terraform bootstrap
```bash
terraform -chdir=terraform/vault/dev init -input=false
terraform -chdir=terraform/vault/dev apply -input=false -auto-approve
```
이 apply 는 아래를 선언적으로 맞춥니다.
- `kv`, `database`, `transit` mount 활성화
- Kubernetes auth backend / role reconcile
- AppRole backend / workflow AppRole reconcile
- dev runtime KV 를 provider Vault seed 에 맞춰 동기화
- JWT transit key 생성
- Postgres database backend / dynamic role 정의
- `kv/dev/workload/bootstrap` 에 workload workflow AppRole credential publish
필요하면 output 으로 workload workflow AppRole 값을 직접 확인할 수 있습니다.
```bash
terraform -chdir=terraform/vault/dev output workflow_role_id
terraform -chdir=terraform/vault/dev output -raw workflow_secret_id
```
### 정책 분리
현재 dev 정책은 아래처럼 역할별로 나눕니다.
- `auth-server-dev`
이유: 앱 런타임은 `platform/postgres/auth-server`, `platform/keycloak/client-auth-server`, JWT transit signing만 접근하면 충분합니다.
- `auth-db-migration-dev`
이유: migration job 은 `database/creds/auth-db-migration-dev` 로 짧은 DB credential 을 받아 실행합니다.
- `postgres-dev`
이유: DB pod 는 `platform/postgres/superuser`, `platform/postgres/auth-server`, `platform/postgres/keycloak` 만 읽으면 됩니다.
- `keycloak-dev`
이유: Keycloak pod 는 `platform/postgres/keycloak`, `platform/keycloak/bootstrap-admin` 만 읽으면 됩니다.
- `keycloak-client-sync-dev`
이유: client sync job 은 `platform/keycloak/bootstrap-admin`, `platform/keycloak/client-auth-server` 만 읽으면 됩니다.
- `workload-automation-dev`
이유: Terraform apply 가 policy, role, auth, transit, KV, AppRole, database 설정을 모두 reconcile 합니다.
- `platform-admin-dev`
이유: 사람이 비상 복구나 수동 운영 작업을 할 때 쓰는 운영자 정책입니다.
- `postgres-operator-dev`
이유: 사람이 dev DB 에 직접 접속할 때는 `database/creds/postgres-operator-dev` 만 읽는 짧은 토큰으로 제한합니다.
- `keycloak-operator-dev`
이유: 사람이 Keycloak 에 직접 로그인할 때는 bootstrap admin credential 만 읽는 짧은 토큰으로 제한합니다.
### 자동화용 CI Secret
`vault-dev-reconcile` workflow 를 사용하려면 최소 아래 secret 이 필요합니다.
- `KUBECONFIG_DEV_B64`
- `VAULT_TRANSIT_DEV_ROLE_ID`
- `VAULT_TRANSIT_DEV_SECRET_ID`
권장 흐름은 아래와 같습니다.
1. `vault-transit` runbook 으로 provider Vault 를 1회 init / unseal / bootstrap 합니다.
2. provider Vault 에 app seed 값을 입력합니다.
3. workload Vault 를 1회 init 합니다.
4. `terraform/vault/dev` 를 root token 으로 1회 apply 합니다.
5. 이 apply 가 workload workflow AppRole credential 을 `kv/dev/workload/bootstrap` 에 써넣습니다.
6. 이후부터는 workflow 가 provider bootstrap 확인, `terraform/vault/reconcile` apply, Argo CD app apply 를 자동 수행합니다.
bootstrap용 privileged token은 GitHub secret에 올리지 않고 운영자 로컬에서만 사용하는 것을 권장합니다.
### 정적 seed 로 최초 1회 넣어야 하는 값
아래 값들은 최초 1회 사람이 입력하거나 상위 secret source 에서 sync 해야 합니다.
- `kv/dev/workload/platform/postgres/superuser`
값: `POSTGRES_SUPERUSER_PASSWORD`
- `kv/dev/workload/platform/postgres/auth-server`
값: `APP_DATASOURCE_USERNAME`, `APP_DATASOURCE_PASSWORD`, `AUTH_DB_PASSWORD`
- `kv/dev/workload/platform/postgres/keycloak`
값: `KEYCLOAK_DB_PASSWORD`
- `kv/dev/workload/platform/keycloak/bootstrap-admin`
값: `KC_BOOTSTRAP_ADMIN_PASSWORD`
- `kv/dev/workload/platform/keycloak/client-auth-server`
값: `APP_SECURITY_OAUTH2_KEYCLOAK_CLIENT_SECRET`, `KEYCLOAK_CLIENT_SECRET`
이 값들은 provider Vault seed path 가 source of truth 이고, workload Terraform apply 가 이를 workload Vault KV 로 동기화합니다.
### 사람 직접 접근용 토큰 발급
장기 운영자 토큰은 Terraform state 에 저장하지 않습니다.
필요할 때 privileged token 으로 `platform-admin-dev` 토큰을 짧게 발급한 뒤 아래 스크립트를 사용합니다.
```bash
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=<privileged-token>
vault token create -orphan -policy=platform-admin-dev -ttl=1h
```
발급된 `platform-admin-dev` 토큰으로:
```bash
export VAULT_TOKEN=<platform-admin-dev-token>
./scripts/vault/dev/issue-operator-tokens.sh
```
이후:
- `postgres-operator-dev`: `vault read database/creds/postgres-operator-dev`
- `keycloak-operator-dev`: `vault kv get kv/dev/platform/keycloak/bootstrap-admin`
를 수행해 dev 접속 정보를 확인할 수 있습니다.
### 주의
- manual bootstrap state 는 [`.terraform-state/vault-dev.tfstate`](/home/donghyeon/dev/Project-Auth-GitOps/.terraform-state/vault-dev.tfstate) 에 저장됩니다.
- routine reconcile state 는 [`.terraform-state/vault-reconcile.tfstate`](/home/donghyeon/dev/Project-Auth-GitOps/.terraform-state/vault-reconcile.tfstate) 에 저장됩니다.
- Vault provider state 에는 민감한 값이 들어가므로 self-hosted runner 와 로컬 작업 디렉터리를 동일하게 보호해야 합니다.
@@ -0,0 +1,3 @@
path "database/creds/auth-db-migration-dev" {
capabilities = ["read"]
}
@@ -0,0 +1,15 @@
path "kv/data/dev/platform/postgres/auth-server" {
capabilities = ["read"]
}
path "kv/data/dev/platform/keycloak/client-auth-server" {
capabilities = ["read"]
}
path "transit/keys/project-auth-jwt" {
capabilities = ["read"]
}
path "transit/sign/project-auth-jwt" {
capabilities = ["update"]
}
@@ -0,0 +1,7 @@
path "kv/data/dev/platform/keycloak/bootstrap-admin" {
capabilities = ["read"]
}
path "kv/data/dev/platform/keycloak/client-auth-server" {
capabilities = ["read"]
}
@@ -0,0 +1,7 @@
path "kv/data/dev/platform/postgres/keycloak" {
capabilities = ["read"]
}
path "kv/data/dev/platform/keycloak/bootstrap-admin" {
capabilities = ["read"]
}

Some files were not shown because too many files have changed in this diff Show More