Secrets Management
본 문서는 Juju 4.0 코드베이스 분석을 기반으로 AI가 정리했습니다.
Overview
Juju Secrets는 비밀번호, API 키, 인증서 등 민감한 데이터를 charm 간에 안전하게 공유하는 시스템이다. 역할 기반 접근 제어, 자동 회전, 만료 정책, 외부 백엔드 연동을 지원한다.
flowchart TB
subgraph Owner["Secret Owner (MySQL)"]
SA["secret-add"]
SG["secret-grant"]
end
subgraph Consumer["Consumer (WordPress)"]
GET["secret-get"]
end
subgraph Backend["Secret Backend"]
CTRL["Controller\n(built-in)"]
VAULT["Vault\n(external)"]
end
SA -->|"생성"| Backend
SG -->|"grant\n(via relation)"| Consumer
GET -->|"조회"| Backend
Secret URI와 소유권
URI 형식
secret:<id>
secret:<source-uuid>/<id> # cross-model
id는 20자 xid 시간 기반으로 정렬 가능한 고유 ID 생성 알고리즘. MongoDB의 ObjectID와 유사하지만 더 간결하다. 생성 시점 순서가 보장된다. 로 생성된다.
소유권 유형
| 소유자 | 설명 | 관리 주체 |
|---|---|---|
| Application | 앱 수준 secret (DB 패스워드 등) | 리더 유닛만 앱 소유 secret은 리더 유닛만 생성/수정할 수 있다. 리더가 아닌 유닛이 시도하면 PermissionDenied 에러가 발생한다. |
| Unit | 유닛별 secret (TLS 인증서 등) | 해당 유닛 |
| Model | 모델 전체 secret (클라우드 인증) | 모델 관리자 |
Secret 생성
Hook Tool
# 기본 생성
secret-add username=admin password=s3cret
# → secret:clm2k9v2p0s73e3rgtdg
# 옵션
secret-add --owner app \
--rotate monthly \
--expire 720h \
--label db-credentials \
--description "Database credentials" \
username=admin password=s3cret
내부 흐름
파일: domain/secret/service/service.go
CreateCharmSecret()
→ 활성 백엔드 조회
→ backend.SaveContent(uri, revision, value)
- 외부 백엔드: Vault/K8s에 저장 → ValueRef 반환
- 내장 백엔드: dqlite에 직접 저장
→ DB 레코드 생성
- secret 메타데이터
- secret_revision (버전 1)
- secret_content 또는 secret_value_ref
- 소유자 매핑 (app/unit/model)
- rotation 스케줄 (선택)
- expiry 시간 (선택)
dqlite 테이블 구조
| 테이블 | 용도 |
|---|---|
secret | 기본 엔티티 |
secret_metadata | 버전, 설명, rotate policy, auto_prune, checksum |
secret_revision | 리비전별 생성/수정 시간 |
secret_content | 내장 백엔드 시 key-value 저장 |
secret_value_ref | 외부 백엔드 참조 (backend_uuid, revision_id) |
secret_application_owner | 앱 소유 매핑 |
secret_unit_owner | 유닛 소유 매핑 |
접근 제어 (Grant/Revoke)
역할과 스코프
| 역할 | 권한 |
|---|---|
| Manage | 생성, 수정, 삭제, grant/revoke |
| View | 읽기만 가능 |
| None | 접근 불가 (revoke 상태) |
| 스코프 | 설명 |
|---|---|
| Relation | 특정 관계 가장 일반적인 grant 스코프. 특정 관계를 통해 연결된 앱에만 secret을 공개한다. 관계가 해제되면 접근도 자동으로 차단된다. 를 통한 접근 |
| Application | 앱의 모든 유닛에 접근 허용 |
| Unit | 특정 유닛에만 허용 |
| Model | 모델 전체 |
Grant 흐름
# relation hook 내에서 grant
secret-grant secret:clm2k9v2p0s73e3rgtdg --relation db:0
GrantSecretAccess()
→ subject UUID 조회 (대상 앱/유닛)
→ scope UUID 조회 (관계/앱/모델)
→ 관리 권한 확인 (소유자 또는 리더)
→ secret_permission 테이블에 INSERT
- (secret_id, subject_uuid, role, scope_uuid)
불변 규칙: grant된 scope/subject type은 변경 불가. 변경하려면 revoke 후 재grant 해야 한다.
Secret 조회
Hook Tool
# 현재 리비전 조회
secret-get secret:clm2k9v2p0s73e3rgtdg
# → username: admin
# → password: s3cret
# 최신 리비전으로 갱신
secret-get --refresh secret:clm2k9v2p0s73e3rgtdg
# 최신 리비전 미리보기 (갱신 안 함)
secret-get --peek secret:clm2k9v2p0s73e3rgtdg
# 특정 키만
secret-get secret:clm2k9v2p0s73e3rgtdg password
Consumer 추적
각 유닛은 자신이 마지막으로 읽은 리비전을 기억한다:
| 플래그 | 동작 |
|---|---|
| (없음) | 저장된 current_revision 반환 |
--refresh | 최신 리비전 반환 + current_revision 업데이트 |
--peek | 최신 리비전 반환, current_revision 변경 없음 |
secret_unit_consumer 테이블에서 유닛별 소비 상태를 추적한다.
Secret 회전 (Rotation)
파일: internal/worker/secretrotate/secretrotate.go
회전 정책
| 정책 | 주기 |
|---|---|
hourly | 1시간 |
daily | 24시간 |
weekly | 7일 |
monthly | 1개월 |
quarterly | 3개월 |
yearly | 1년 |
회전 워커 흐름
flowchart LR
W["SecretRotate\nWorker"] -->|"타이머 만료"| U["Uniter"]
U -->|"secret-rotate hook"| C["Charm"]
C -->|"secret-set\n(새 값)"| DB[(dqlite)]
C -->|"secret-rotate\n(완료 보고)"| W
- SecretRotate 워커 소유 secret의 회전 스케줄을 감시하는 워커. 다음 회전 시각을 계산하여 타이머를 관리하고, 만료 시 Uniter에 secret URI를 전달한다. 가
secret_rotation.next_rotation_time감시 - 시간 도달 시 Uniter에 secret URI 전달
- Uniter가 charm의
secret-rotatehook 실행 - Charm이
secret-set으로 새 값 설정 - Charm이
secret-rotate로 회전 완료 보고 - 워커가 다음 회전 시각 계산 및 저장
Secret 만료 (Expiry)
파일: internal/worker/secretexpire/secretexpire.go
리비전별 만료
# 생성 시 만료 설정
secret-add --expire 720h password=temp123
# 업데이트 시 만료 설정
secret-set secret:xxx --expire 24h password=new456
만료는 secret 전체가 아니라 리비전별로 관리된다:
SecretExpire 워커
→ WatchSecretRevisionsExpiryChanges() // 만료 예정 리비전 감시
→ 타이머 만료 시
→ 외부 백엔드에서 content 삭제
→ secret_revision_obsolete에 pending_delete 마킹
→ Pruner 워커가 실제 DB 삭제
Secret Backend 아키텍처
백엔드 유형
| 백엔드 | 저장소 | 용도 |
|---|---|---|
| Controller (built-in) | dqlite controller의 dqlite DB에 직접 저장. 별도 외부 시스템 없이 동작하여 설정이 간편하지만, 대규모 환경에서는 외부 백엔드를 권장한다. | 기본, 소규모 환경 |
| Kubernetes | K8s Secrets | CAAS 모델 기본 |
| Vault | HashiCorp Vault | 엔터프라이즈, 규정 준수 |
백엔드 인터페이스
type SecretsBackend interface {
SaveContent(ctx, uri, revision, value) (revisionID, error)
ReadContent(ctx, revisionID) (content, error)
DeleteContent(ctx, revisionID) error
RestrictedConfig() (*Params, error) // consumer용 제한된 설정
}
Backend Token Rotation
파일: internal/worker/secretbackendrotate/rotate.go
외부 백엔드(Vault 등)의 인증 토큰을 주기적으로 갱신:
SecretBackendRotate 워커
→ WatchTokenRotationChanges()
→ 백엔드별 token_rotate_interval 확인
→ RotateBackendTokens(backendIDs)
→ Vault token refresh 등 프로바이더별 처리
Secret Draining (백엔드 마이그레이션)
파일: internal/worker/secretsdrainworker/worker.go
모델의 secret 백엔드가 변경되면 기존 secret을 새 백엔드로 이동:
flowchart LR
OLD["기존 백엔드\n(Controller)"] -->|"ReadContent"| DRAIN["Drain Worker"]
DRAIN -->|"SaveContent"| NEW["새 백엔드\n(Vault)"]
DRAIN -->|"UpdateRef"| DB[(dqlite)]
WatchSecretBackendChanged()감지GetSecretsToDrain()조회- 리더: 앱 소유 + 유닛 소유 secret
- 비리더: 유닛 소유만
- 기존 백엔드에서 읽기 → 새 백엔드에 저장
secret_value_ref업데이트- 레퍼런스 카운트 조정 (새 백엔드 +1, 기존 -1)
리더십 안전성 앱 소유 secret의 draining은 리더 유닛만 수행한다. 리더십이 변경되면 WithStableLeadership()으로 안전하게 재시도한다. :
LeadershipTracker.WithStableLeadership()으로 drain 중 리더 변경을 감지하여 재시도
Secret Pruning
파일: internal/worker/secretspruner/worker.go
사용되지 않는 오래된 리비전을 자동 삭제:
auto_prune = true인 user secret에만 적용 (charm secret 미지원)secret_revision_obsolete에pending_delete마킹된 리비전 대상secret_content+secret_revision삭제- 외부 백엔드의 레퍼런스도 정리
Cross-Model Secrets
다른 모델의 앱에게 secret을 공유할 때:
- Macaroon HTTP 쿠키의 확장으로, 분산 시스템에서 위임 가능한 인증 토큰. Juju는 cross-model secret 접근 시 macaroon으로 접근 권한을 증명한다. 기반 인가
- consumer에게는
RestrictedConfig()로 제한된 백엔드 설정만 전달 secret_remote_unit_consumer테이블로 원격 consumer 추적
핵심 포인트
- 역할 기반 접근 제어: Manage > View > None 계층, 관계 스코프로 범위 제한
- 리더십 연동: 앱 소유 secret은 리더 유닛만 관리 가능
- 리비전 관리: 수정 시 새 리비전 생성, consumer는 각자 읽은 리비전을 추적
- 자동 회전: 정책 설정 시 워커가 스케줄 관리, charm hook으로 값 갱신
- 리비전별 만료: secret 전체가 아니라 개별 리비전에 만료 시간 설정
- 백엔드 추상화: Controller/K8s/Vault 간 투명한 전환, drain 워커로 무중단 마이그레이션
- Cross-Model: Macaroon 기반 인가로 다른 모델의 앱에 안전하게 공유
관련 문서
- Leadership Management — 앱 소유 secret 관리 시 필요한 리더십 메커니즘
- Relations — secret grant의 가장 일반적인 스코프인 relation을 통한 접근 제어
- Charm Deployment — hook tool (secret-add/get/set)이 실행되는 환경
- Change Stream — secret 변경 이벤트가 rotation/expiry 워커에 전파되는 경로