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

회전 정책

정책주기
hourly1시간
daily24시간
weekly7일
monthly1개월
quarterly3개월
yearly1년

회전 워커 흐름

flowchart LR
    W["SecretRotate\nWorker"] -->|"타이머 만료"| U["Uniter"]
    U -->|"secret-rotate hook"| C["Charm"]
    C -->|"secret-set\n(새 값)"| DB[(dqlite)]
    C -->|"secret-rotate\n(완료 보고)"| W
  1. SecretRotate 워커 소유 secret의 회전 스케줄을 감시하는 워커. 다음 회전 시각을 계산하여 타이머를 관리하고, 만료 시 Uniter에 secret URI를 전달한다. secret_rotation.next_rotation_time 감시
  2. 시간 도달 시 Uniter에 secret URI 전달
  3. Uniter가 charm의 secret-rotate hook 실행
  4. Charm이 secret-set으로 새 값 설정
  5. Charm이 secret-rotate로 회전 완료 보고
  6. 워커가 다음 회전 시각 계산 및 저장

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에 직접 저장. 별도 외부 시스템 없이 동작하여 설정이 간편하지만, 대규모 환경에서는 외부 백엔드를 권장한다. 기본, 소규모 환경
KubernetesK8s SecretsCAAS 모델 기본
VaultHashiCorp 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)]
  1. WatchSecretBackendChanged() 감지
  2. GetSecretsToDrain() 조회
    • 리더: 앱 소유 + 유닛 소유 secret
    • 비리더: 유닛 소유만
  3. 기존 백엔드에서 읽기 → 새 백엔드에 저장
  4. secret_value_ref 업데이트
  5. 레퍼런스 카운트 조정 (새 백엔드 +1, 기존 -1)

리더십 안전성 앱 소유 secret의 draining은 리더 유닛만 수행한다. 리더십이 변경되면 WithStableLeadership()으로 안전하게 재시도한다. : LeadershipTracker.WithStableLeadership()으로 drain 중 리더 변경을 감지하여 재시도


Secret Pruning

파일: internal/worker/secretspruner/worker.go

사용되지 않는 오래된 리비전을 자동 삭제:

  1. auto_prune = true인 user secret에만 적용 (charm secret 미지원)
  2. secret_revision_obsoletepending_delete 마킹된 리비전 대상
  3. secret_content + secret_revision 삭제
  4. 외부 백엔드의 레퍼런스도 정리

Cross-Model Secrets

다른 모델의 앱에게 secret을 공유할 때:

  • Macaroon HTTP 쿠키의 확장으로, 분산 시스템에서 위임 가능한 인증 토큰. Juju는 cross-model secret 접근 시 macaroon으로 접근 권한을 증명한다. 기반 인가
  • consumer에게는 RestrictedConfig()로 제한된 백엔드 설정만 전달
  • secret_remote_unit_consumer 테이블로 원격 consumer 추적

핵심 포인트

  1. 역할 기반 접근 제어: Manage > View > None 계층, 관계 스코프로 범위 제한
  2. 리더십 연동: 앱 소유 secret은 리더 유닛만 관리 가능
  3. 리비전 관리: 수정 시 새 리비전 생성, consumer는 각자 읽은 리비전을 추적
  4. 자동 회전: 정책 설정 시 워커가 스케줄 관리, charm hook으로 값 갱신
  5. 리비전별 만료: secret 전체가 아니라 개별 리비전에 만료 시간 설정
  6. 백엔드 추상화: Controller/K8s/Vault 간 투명한 전환, drain 워커로 무중단 마이그레이션
  7. Cross-Model: Macaroon 기반 인가로 다른 모델의 앱에 안전하게 공유

관련 문서

  • Leadership Management — 앱 소유 secret 관리 시 필요한 리더십 메커니즘
  • Relations — secret grant의 가장 일반적인 스코프인 relation을 통한 접근 제어
  • Charm Deployment — hook tool (secret-add/get/set)이 실행되는 환경
  • Change Stream — secret 변경 이벤트가 rotation/expiry 워커에 전파되는 경로