643 lines
18 KiB
Markdown
643 lines
18 KiB
Markdown
# config / secrets 예시
|
|
|
|
모든 YAML은 `kubectl apply --dry-run=server -f -` 기준 clean. VSO는 Helm chart `hashicorp/vault-secrets-operator`로 `vault-secrets-operator` namespace에 설치되어 있고, Vault는 `vault` namespace(`https://vault.vault.svc:8200`)에서 기동 중이며, Kubernetes auth method(`auth/kubernetes`)가 활성화되어 있다고 가정한다.
|
|
|
|
---
|
|
|
|
## 좋은 예시 1: 비기밀 ConfigMap (hash-suffixed by Kustomize)
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: auth-server-config
|
|
namespace: auth-prod
|
|
labels:
|
|
app.kubernetes.io/name: auth-server
|
|
app.kubernetes.io/part-of: identity-platform
|
|
data:
|
|
application.yaml: |
|
|
server:
|
|
port: 8080
|
|
shutdown: graceful
|
|
management:
|
|
endpoints:
|
|
web:
|
|
base-path: /actuator
|
|
exposure:
|
|
include: health,info,prometheus
|
|
server:
|
|
port: 9090
|
|
spring:
|
|
main:
|
|
banner-mode: off
|
|
datasource:
|
|
hikari:
|
|
maximum-pool-size: 20
|
|
connection-timeout: 5000
|
|
logging:
|
|
level:
|
|
root: INFO
|
|
com.example.auth: INFO
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- 비밀값은 하나도 없다(username/password/url 제외). Hikari pool size, log level, actuator 경로 같은 operational config만.
|
|
- Kustomize `configMapGenerator`로 hash suffix를 붙이면 Deployment가 자동 rollout.
|
|
|
|
---
|
|
|
|
## 좋은 예시 2: VSO 전체 스택 (VaultConnection + VaultAuth + VaultStaticSecret + VaultDynamicSecret + VaultPKISecret)
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Namespace
|
|
metadata:
|
|
name: auth-prod
|
|
labels:
|
|
pod-security.kubernetes.io/enforce: restricted
|
|
pod-security.kubernetes.io/enforce-version: v1.29
|
|
---
|
|
apiVersion: v1
|
|
kind: ServiceAccount
|
|
metadata:
|
|
name: auth-server
|
|
namespace: auth-prod
|
|
automountServiceAccountToken: true
|
|
---
|
|
apiVersion: secrets.hashicorp.com/v1beta1
|
|
kind: VaultConnection
|
|
metadata:
|
|
name: vault
|
|
namespace: auth-prod
|
|
spec:
|
|
address: https://vault.vault.svc:8200
|
|
skipTLSVerify: false
|
|
caCertSecretRef: vault-ca-bundle
|
|
headers: {}
|
|
---
|
|
apiVersion: secrets.hashicorp.com/v1beta1
|
|
kind: VaultAuth
|
|
metadata:
|
|
name: auth-server
|
|
namespace: auth-prod
|
|
spec:
|
|
vaultConnectionRef: vault
|
|
method: kubernetes
|
|
mount: kubernetes
|
|
kubernetes:
|
|
role: auth-server
|
|
serviceAccount: auth-server
|
|
audiences:
|
|
- vault
|
|
---
|
|
apiVersion: secrets.hashicorp.com/v1beta1
|
|
kind: VaultStaticSecret
|
|
metadata:
|
|
name: auth-server-oidc-client
|
|
namespace: auth-prod
|
|
spec:
|
|
vaultAuthRef: auth-server
|
|
mount: kv
|
|
type: kv-v2
|
|
path: identity/auth-server/prod/oidc
|
|
refreshAfter: 1h
|
|
destination:
|
|
name: auth-server-oidc-client
|
|
create: true
|
|
type: Opaque
|
|
rolloutRestartTargets:
|
|
- kind: Deployment
|
|
name: auth-server
|
|
---
|
|
apiVersion: secrets.hashicorp.com/v1beta1
|
|
kind: VaultDynamicSecret
|
|
metadata:
|
|
name: auth-server-db
|
|
namespace: auth-prod
|
|
spec:
|
|
vaultAuthRef: auth-server
|
|
mount: database
|
|
path: creds/auth-server-role
|
|
destination:
|
|
name: auth-server-db
|
|
create: true
|
|
type: Opaque
|
|
transformation:
|
|
templates:
|
|
DB_URL:
|
|
text: 'jdbc:postgresql://identity-postgres.data-prod.svc:5432/auth?user={{ .Secrets.username }}&password={{ .Secrets.password }}&sslmode=require'
|
|
DB_USERNAME:
|
|
text: '{{ .Secrets.username }}'
|
|
DB_PASSWORD:
|
|
text: '{{ .Secrets.password }}'
|
|
rolloutRestartTargets:
|
|
- kind: Deployment
|
|
name: auth-server
|
|
---
|
|
apiVersion: secrets.hashicorp.com/v1beta1
|
|
kind: VaultPKISecret
|
|
metadata:
|
|
name: auth-server-internal-tls
|
|
namespace: auth-prod
|
|
spec:
|
|
vaultAuthRef: auth-server
|
|
mount: pki_int
|
|
role: auth-server
|
|
commonName: auth-server.auth-prod.svc
|
|
altNames:
|
|
- auth-server.auth-prod.svc.cluster.local
|
|
- auth-server
|
|
ttl: 24h
|
|
destination:
|
|
name: auth-server-internal-tls
|
|
create: true
|
|
type: kubernetes.io/tls
|
|
rolloutRestartTargets:
|
|
- kind: Deployment
|
|
name: auth-server
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- Vault가 source of truth. 모든 비밀이 `kv/identity/auth-server/prod/*` 또는 database/PKI engine에서 발급.
|
|
- VSO가 결과물을 표준 Kubernetes Secret(`Opaque`, `kubernetes.io/tls`)으로 materialize.
|
|
- Dynamic DB credential은 Postgres role에서 TTL 기반 자동 발급/폐기. Rotation 시 `rolloutRestartTargets`로 Deployment rolling restart.
|
|
- PKI Secret은 `kubernetes.io/tls` 타입 → Traefik/앱 TLS에 그대로 소비 가능.
|
|
|
|
---
|
|
|
|
## 좋은 예시 3: VSO Secret을 소비하는 Deployment (envFrom + volume 혼합)
|
|
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: auth-server
|
|
namespace: auth-prod
|
|
labels:
|
|
app.kubernetes.io/name: auth-server
|
|
app.kubernetes.io/part-of: identity-platform
|
|
app.kubernetes.io/version: 1.42.0
|
|
app.kubernetes.io/managed-by: argocd
|
|
spec:
|
|
replicas: 6
|
|
revisionHistoryLimit: 5
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: auth-server
|
|
app.kubernetes.io/instance: auth-server
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: auth-server
|
|
app.kubernetes.io/instance: auth-server
|
|
app.kubernetes.io/part-of: identity-platform
|
|
app.kubernetes.io/version: 1.42.0
|
|
spec:
|
|
serviceAccountName: auth-server
|
|
automountServiceAccountToken: true
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 10001
|
|
runAsGroup: 10001
|
|
fsGroup: 10001
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: auth-server
|
|
image: registry.example.com/identity/auth-server@sha256:8f3c0a8c6b3a2a7a0f1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f6071
|
|
imagePullPolicy: IfNotPresent
|
|
ports:
|
|
- name: http
|
|
containerPort: 8080
|
|
- name: metrics
|
|
containerPort: 9090
|
|
envFrom:
|
|
- configMapRef:
|
|
name: auth-server-config
|
|
- secretRef:
|
|
name: auth-server-db
|
|
- secretRef:
|
|
name: auth-server-oidc-client
|
|
volumeMounts:
|
|
- name: internal-tls
|
|
mountPath: /var/run/secrets/tls
|
|
readOnly: true
|
|
- name: appconfig
|
|
mountPath: /workspace/config
|
|
readOnly: true
|
|
- name: tmp
|
|
mountPath: /tmp
|
|
resources:
|
|
requests:
|
|
cpu: 200m
|
|
memory: 512Mi
|
|
limits:
|
|
cpu: "2"
|
|
memory: 1Gi
|
|
readinessProbe:
|
|
httpGet:
|
|
path: /actuator/health/readiness
|
|
port: http
|
|
livenessProbe:
|
|
httpGet:
|
|
path: /actuator/health/liveness
|
|
port: http
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 10001
|
|
runAsGroup: 10001
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
- name: metrics-exporter
|
|
image: registry.example.com/platform/jmx-exporter@sha256:1111111111111111111111111111111111111111111111111111111111111111
|
|
ports:
|
|
- name: jmx-metrics
|
|
containerPort: 9091
|
|
resources:
|
|
requests:
|
|
cpu: 50m
|
|
memory: 64Mi
|
|
limits:
|
|
cpu: 200m
|
|
memory: 128Mi
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 10001
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumes:
|
|
- name: internal-tls
|
|
secret:
|
|
secretName: auth-server-internal-tls
|
|
defaultMode: 0400
|
|
- name: appconfig
|
|
configMap:
|
|
name: auth-server-config
|
|
- name: tmp
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 64Mi
|
|
imagePullSecrets:
|
|
- name: registry-example-com
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- VSO가 생성한 `auth-server-db`, `auth-server-oidc-client`를 envFrom으로 소비. 앱 코드는 `DB_USERNAME`, `DB_PASSWORD`, `OIDC_CLIENT_SECRET` 환경변수를 읽기만 함.
|
|
- TLS private key는 volume(`/var/run/secrets/tls`, mode 0400)으로만 마운트. env 노출 없음.
|
|
- `metrics-exporter` sidecar에는 **어떤 secret도 envFrom/volumeMount로 전달하지 않는다**. Scope 최소화.
|
|
- image는 digest pin, `imagePullPolicy: IfNotPresent`.
|
|
|
|
---
|
|
|
|
## 나쁜 예시 1: plain Secret manifest + ConfigMap에 비밀 혼재
|
|
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Secret
|
|
metadata:
|
|
name: auth-server-db
|
|
namespace: auth-prod
|
|
type: Opaque
|
|
stringData:
|
|
username: prod-admin
|
|
password: S3cur3P@ssw0rd!
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: auth-server-config
|
|
namespace: auth-prod
|
|
data:
|
|
application.yaml: |
|
|
spring:
|
|
datasource:
|
|
url: jdbc:postgresql://prod-db:5432/auth
|
|
username: prod-admin
|
|
password: S3cur3P@ssw0rd!
|
|
```
|
|
|
|
**문제:**
|
|
|
|
- 운영 비밀이 Git에 평문으로 커밋된다. base64/stringData 여부와 무관.
|
|
- ConfigMap에 password가 들어가 있음 → RBAC `configmaps:get` 권한을 가진 모든 SA가 읽을 수 있음.
|
|
- secret source가 두 곳에 있어 회전 불가능.
|
|
- VSO/ESO/SealedSecrets 어느 경로에도 부합하지 않음.
|
|
|
|
---
|
|
|
|
## 좋은 예시 4: ImagePullSecret을 VSO로 Vault에서 sync
|
|
|
|
```yaml
|
|
apiVersion: secrets.hashicorp.com/v1beta1
|
|
kind: VaultStaticSecret
|
|
metadata:
|
|
name: registry-example-com
|
|
namespace: auth-prod
|
|
spec:
|
|
vaultAuthRef: auth-server
|
|
mount: kv
|
|
type: kv-v2
|
|
path: platform/registry/example-com
|
|
refreshAfter: 24h
|
|
destination:
|
|
name: registry-example-com
|
|
create: true
|
|
type: kubernetes.io/dockerconfigjson
|
|
transformation:
|
|
templates:
|
|
.dockerconfigjson:
|
|
text: |
|
|
{
|
|
"auths": {
|
|
"registry.example.com": {
|
|
"username": "{{ .Secrets.username }}",
|
|
"password": "{{ .Secrets.password }}",
|
|
"auth": "{{ printf "%s:%s" .Secrets.username .Secrets.password | b64enc }}"
|
|
}
|
|
}
|
|
}
|
|
---
|
|
apiVersion: v1
|
|
kind: ServiceAccount
|
|
metadata:
|
|
name: auth-server
|
|
namespace: auth-prod
|
|
automountServiceAccountToken: true
|
|
imagePullSecrets:
|
|
- name: registry-example-com
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- registry credential도 Vault가 SoT. 하드코딩 없음.
|
|
- VSO가 `kubernetes.io/dockerconfigjson` 타입 Secret을 생성. kubelet이 바로 인식.
|
|
- SA에 묶여 있어 Deployment마다 imagePullSecrets 반복 선언 불필요.
|
|
|
|
---
|
|
|
|
## 좋은 예시 5: cert-manager + VSO 비교 — Ingress TLS는 cert-manager, internal mTLS는 VSO PKI
|
|
|
|
cert-manager가 외부 공인 도메인용 `kubernetes.io/tls` Secret을 발급하고, VSO `VaultPKISecret`은 internal service mesh mTLS용 단기 인증서를 발급한다. 두 경로 모두 최종 형태는 `kubernetes.io/tls` Secret으로 동일하므로 앱은 secret name만 구분한다.
|
|
|
|
```yaml
|
|
apiVersion: cert-manager.io/v1
|
|
kind: Certificate
|
|
metadata:
|
|
name: auth-example-com
|
|
namespace: auth-prod
|
|
spec:
|
|
secretName: auth-example-com-tls
|
|
issuerRef:
|
|
kind: ClusterIssuer
|
|
name: letsencrypt-prod
|
|
dnsNames:
|
|
- auth.example.com
|
|
duration: 2160h
|
|
renewBefore: 360h
|
|
---
|
|
apiVersion: secrets.hashicorp.com/v1beta1
|
|
kind: VaultPKISecret
|
|
metadata:
|
|
name: auth-server-internal-tls
|
|
namespace: auth-prod
|
|
spec:
|
|
vaultAuthRef: auth-server
|
|
mount: pki_int
|
|
role: auth-server
|
|
commonName: auth-server.auth-prod.svc
|
|
ttl: 24h
|
|
destination:
|
|
name: auth-server-internal-tls
|
|
create: true
|
|
type: kubernetes.io/tls
|
|
rolloutRestartTargets:
|
|
- kind: Deployment
|
|
name: auth-server
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- 외부 ACME 인증서는 공인 CA(Let's Encrypt), 내부는 조직 CA(Vault PKI)로 분리.
|
|
- 둘 다 같은 Secret 타입이라 Traefik/앱이 동일하게 소비 가능.
|
|
- VSO PKI는 24h TTL로 짧게 회전 → lateral movement window 최소화.
|
|
|
|
---
|
|
|
|
## 좋은 예시 6: Vault Agent Injector가 K8s Secret 없이 파일로 템플릿 렌더링
|
|
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: legacy-report-generator
|
|
namespace: reports-prod
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app.kubernetes.io/name: report-generator
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app.kubernetes.io/name: report-generator
|
|
annotations:
|
|
vault.hashicorp.com/agent-inject: "true"
|
|
vault.hashicorp.com/role: "report-generator"
|
|
vault.hashicorp.com/agent-inject-secret-report.conf: "kv/data/reports/smtp"
|
|
vault.hashicorp.com/agent-inject-template-report.conf: |
|
|
{{- with secret "kv/data/reports/smtp" -}}
|
|
[smtp]
|
|
host = {{ .Data.data.host }}
|
|
port = {{ .Data.data.port }}
|
|
username = {{ .Data.data.username }}
|
|
password = {{ .Data.data.password }}
|
|
{{- end }}
|
|
vault.hashicorp.com/secret-volume-path-report.conf: "/vault/secrets"
|
|
vault.hashicorp.com/agent-inject-containers: "report-generator"
|
|
vault.hashicorp.com/agent-run-as-user: "10001"
|
|
vault.hashicorp.com/agent-run-as-group: "10001"
|
|
spec:
|
|
serviceAccountName: report-generator
|
|
automountServiceAccountToken: true
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 10001
|
|
runAsGroup: 10001
|
|
fsGroup: 10001
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
containers:
|
|
- name: report-generator
|
|
image: registry.example.com/reports/generator@sha256:2222222222222222222222222222222222222222222222222222222222222222
|
|
ports:
|
|
- name: http
|
|
containerPort: 8080
|
|
volumeMounts:
|
|
- name: tmp
|
|
mountPath: /tmp
|
|
resources:
|
|
requests:
|
|
cpu: 100m
|
|
memory: 256Mi
|
|
limits:
|
|
cpu: 500m
|
|
memory: 512Mi
|
|
securityContext:
|
|
runAsNonRoot: true
|
|
runAsUser: 10001
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop: ["ALL"]
|
|
seccompProfile:
|
|
type: RuntimeDefault
|
|
volumes:
|
|
- name: tmp
|
|
emptyDir:
|
|
medium: Memory
|
|
sizeLimit: 32Mi
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- K8s Secret object가 **생성되지 않는다**. RBAC audit이 Secret API 호출 없이 Vault audit log로 대체된다.
|
|
- Vault Agent sidecar가 tmpfs에 템플릿 렌더링 → 앱은 파일만 읽음.
|
|
- legacy 앱이 INI/TOML 포맷 설정 파일을 요구할 때 적합.
|
|
|
|
**VSO vs Vault Agent Injector:**
|
|
|
|
| 항목 | VSO | Vault Agent Injector |
|
|
|---|---|---|
|
|
| 결과 | K8s Secret | Pod tmpfs 파일 |
|
|
| K8s API 노출 | Secret object 존재 | 없음 |
|
|
| 소비 방식 | envFrom/volume | file read |
|
|
| 회전 시 | `rolloutRestartTargets` | Agent re-render(인메모리) |
|
|
| 복잡도 | 낮음(CRD만) | 높음(sidecar/init) |
|
|
| 권장 | **운영 기본** | 템플릿/legacy 앱 |
|
|
|
|
---
|
|
|
|
## 나쁜 예시 2: Vault Injector annotation을 모든 컨테이너에 적용 + env 렌더링
|
|
|
|
```yaml
|
|
metadata:
|
|
annotations:
|
|
vault.hashicorp.com/agent-inject: "true"
|
|
vault.hashicorp.com/role: "auth-server"
|
|
vault.hashicorp.com/agent-inject-secret-db: "kv/data/auth-server/db"
|
|
vault.hashicorp.com/agent-inject-template-db: |
|
|
{{- with secret "kv/data/auth-server/db" -}}
|
|
export DB_USERNAME={{ .Data.data.username }}
|
|
export DB_PASSWORD={{ .Data.data.password }}
|
|
{{- end }}
|
|
```
|
|
|
|
**문제:**
|
|
|
|
- `agent-inject-containers` 미지정 → sidecar(metrics, proxy) 포함 모든 컨테이너의 `/vault/secrets`가 보임.
|
|
- `export DB_PASSWORD=...`를 `source`로 읽는 launcher 스크립트 → process env로 비밀이 흘러 `/proc/<pid>/environ` 노출.
|
|
- dynamic lease renew를 활용하지 못하고, 회전 시 rollout trigger 없음.
|
|
|
|
---
|
|
|
|
## 좋은 예시 7: SealedSecret (VSO 미도입 환경/bootstrap)
|
|
|
|
```yaml
|
|
apiVersion: bitnami.com/v1alpha1
|
|
kind: SealedSecret
|
|
metadata:
|
|
name: vault-bootstrap-token
|
|
namespace: vault
|
|
spec:
|
|
encryptedData:
|
|
token: AgCd9sK... (public key로 암호화된 blob)
|
|
template:
|
|
metadata:
|
|
name: vault-bootstrap-token
|
|
namespace: vault
|
|
type: Opaque
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- Git 커밋 가능(public key로 암호화, cluster controller만 복호화).
|
|
- VSO 자체를 기동하기 위한 bootstrap credential(Vault root token, unseal key 대신 KMS auto-unseal 권장)에 적합.
|
|
- SealedSecrets controller가 `Secret`을 namespace에 materialize.
|
|
|
|
**주의:**
|
|
|
|
- 운영에서 **VSO가 기동되면 SealedSecrets 경로는 최소화**. 이중 source of truth 방지.
|
|
- Key 회전은 controller의 sealing key rotation 절차 준수.
|
|
|
|
---
|
|
|
|
## 좋은 예시 8: EncryptionConfiguration for Secret at-rest (API Server 레벨)
|
|
|
|
```yaml
|
|
apiVersion: apiserver.config.k8s.io/v1
|
|
kind: EncryptionConfiguration
|
|
resources:
|
|
- resources:
|
|
- secrets
|
|
providers:
|
|
- kms:
|
|
apiVersion: v2
|
|
name: platform-kms-v2
|
|
endpoint: unix:///var/run/kmsplugin/socket.sock
|
|
timeout: 3s
|
|
- aescbc:
|
|
keys:
|
|
- name: fallback-2026-q1
|
|
secret: <kms-fallback-key-base64>
|
|
- identity: {}
|
|
```
|
|
|
|
**K3s 활성화:**
|
|
|
|
```yaml
|
|
# /etc/rancher/k3s/config.yaml
|
|
secrets-encryption: true
|
|
kube-apiserver-arg:
|
|
- "encryption-provider-config=/etc/rancher/k3s/encryption-config.yaml"
|
|
- "audit-policy-file=/etc/rancher/k3s/audit-policy.yaml"
|
|
- "audit-log-path=/var/log/k3s-audit.log"
|
|
```
|
|
|
|
**왜 좋은가:**
|
|
|
|
- KMS v2 provider가 primary → envelope encryption, 키는 KMS 외부에 존재.
|
|
- `aescbc`는 fallback. `identity`는 마지막(평문), 기존 Secret을 재암호화하기 전 decryption용.
|
|
- K3s config.yaml에 `secrets-encryption: true`로 선언. 서버 재시작 후 `kubectl get secrets -A -o json | kubectl replace -f -`로 기존 Secret 재암호화.
|
|
|
|
---
|
|
|
|
## 나쁜 예시 3: Kustomize secretGenerator로 운영 비밀 literal
|
|
|
|
```yaml
|
|
# overlays/prod/kustomization.yaml
|
|
secretGenerator:
|
|
- name: auth-server-db
|
|
literals:
|
|
- username=prod-admin
|
|
- password=S3cur3P@ssw0rd!
|
|
```
|
|
|
|
**문제:**
|
|
|
|
- 운영 비밀이 Git에 literal 평문 저장.
|
|
- Kustomize hash suffix는 비밀 보호가 아님.
|
|
- 회전 시 매번 Git 커밋 필요(감사/리뷰 시 비밀 노출).
|
|
- 운영은 VSO/ESO/SealedSecrets 경로로만 비밀을 배포해야 한다.
|