제 v0.1.0 버전 시리즈는 권한 부여에 대해 이야기할 수 없었습니다. 왜냐하면 그런 것이 존재하지 않았기 때문입니다. v0.2.0에서는 RBAC-lite, 범위 지정 API 키(scoped API keys), 그리고 HivePlane의 암호화된 비밀 금고를 배포했습니다. 그런데 필드 테스트가 저에게 가르쳐준 첫 번째 교훈은 **귀속성(attribution)에 콜드 스타트 문제(cold-start problem)**가 있다는 것입니다.
저는 역할(roles), 권한(permissions), 감사 추적(audit trail)이 있는 제어 평면을 배포했습니다. 하지만 한 시간 동안 이 모든 것이 접근 불가능했는데, 그 이유는 첫 번째 API 키를 발급할 방법이 없었기 때문입니다.
이 글은 제가 이전 시리즈에서 주장했던 모든 거버넌스 주장의 전제 조건에 관한 것입니다. 바로 '주체(principal)'가 존재해야 한다는 것입니다. 이것이 없다면, 당신의 감사 로그는 허구일 뿐입니다.
콜드 스타트
발견된 문제는 D-10이었습니다. RBAC은 연결되어 있었고, /auth/login 엔드포인트가 존재했습니다. 범위 지정 키가 구현되었으며, 전체 모델이 처음 관리자 키를 발급할 HTTP 부트스트랩(bootstrap)이 없었기 때문에 종단 간(end-to-end)으로 접근할 수 없었습니다. 역할은 존재했지만, 아무도 그 역할을 맡을 수 없었습니다.
해결책은 개발용 환경 관리자 키였습니다. 즉, 환경 변수에서 첫 번째 주체를 부팅한 다음, 거기서 범위 지정 키를 발급하는 방식이었습니다:
# 첫 번째 주체는 API 외부에서 와야 합니다.
export HIVEPLANE_AUTH__ENABLED=true
export HIVEPLANE_AUTH__ADMIN_KEY=$(openssl rand -hex 32)
...
교훈은 **인증(authn)이 운영자들에게 진입 게이트(admission gate)**라는 것입니다. 주체가 존재하기 전까지는
발견 사항 #560, #561, 그리고 #541은 모두 같은 형태였습니다: 액션 자체는 실재했지만, 주체(attribution)를 지정하는 것은 호출자가 임의로 선택한 문자열이었습니다. 해결책은 주체가 행위자(actor)를 해석하도록 하고, 해당 필드를 읽기 전용으로 만드는 것입니다. 자신에게는 액션을 귀속시킬 수 있지만, 다른 사람에게는 할 수 없습니다.
권한 검사가 전혀 없었던 엔드포인트들
그 후 필드 테스트는 RBAC(Role-Based Access Control)이 커버해야 했으나 놓친 허점들을 발견했습니다:
- #537 — 레지스트리 변경 엔드포인트에는 권한 검사가 없었습니다. **뷰어(viewer)**가
DELETE /workloads를 실행할 수 있었습니다.
- #526 — 파괴적인 면역 체계 엔드포인트(격리, 킬 스위치)는 게이트가 없었습니다.
- #563 — 일시 중지/재개/중지 기능에는 권한 게이트가 없어, 뷰어가 어떤 실행이라도 중지할 수 있었습니다.
- #566 — UI 세션 시크릿이 하드코딩된 공개 기본값을 포함하고 있었습니다.
파괴적인 동사(destructive verbs)에 대한 접근을 제한하지 않는 역할 시스템은 이름뿐인 역할 시스템입니다. 해결책은 모든 특권 행동(승인, 승격, 킬 스위치 등)에 대해 서버 측 권한 부여를 하는 것입니다. 뷰어는 승인할 수 없습니다. 뷰어는 중지시킬 수 없습니다. 뷰어는 워크로드를 삭제할 수 없습니다. 이 401/403 응답 코드가 바로 제품입니다.
Bearer-first가 아닌 HMAC-first
제가 예상치 못했던 것은 #515였습니다. 인증이 활성화된 상태에서 GitHub와 Alertmanager 웹훅은 HMAC 서명이 검증되기 전에 401을 받았습니다. Bearer-first 모델은 3개 이상의 소스에서 발생하는 이벤트 수집을 방해했고, 이는 출시 게이트(release gate)가 됩니다. 트리거는 자율성 계층입니다. 만약 인증할 수 없다면, 플릿(fleet)이 스스로 작동하지 못합니다.
해결책은 웹훅 수집이 Bearer-first가 아닌 HMAC-first여야 한다는 것입니다. 서명이 필요한 Bearer 토큰보다 먼저 검증되어야 하며; 트리거 소스는 로그인 방식이 아니라 그 자체의 HMAC에 의해 신뢰를 얻어야 합니다. 자율성과 권한 부여는 공존해야만 합니다. 그렇지 않으면, 비행기(plane)를 본래 목적에 맞게 보호하지 못하게 됩니다.
제가 배운 것들
첫 번째 주체(principal)를 부트스트랩하거나, 아니면 아무것도 존재하지 않는다—환경 관리자 키(env admin key)가 오퍼레이터에게는 입장 게이트 역할을 한다. 출처(Attribution)는 페이로드(payload)가 아니라 주체로부터 나온다: 클라이언트가 제공한 actor는 구조적으로 부인 가능(repudiation)하다. 파괴적인 동사들(destructive verbs)을 통제하라—워크로드를 삭제할 수 있는 뷰어(viewer)는 역할(role)을 가진 것이 아니라 루트 권한을 가졌다. 그리고 웹훅은 HMAC-first여야 한다: 베어러 우선 인증 방식(bearer-first auth)은 이벤트 수집(event ingest)을 망가뜨리며, 자율성(autonomy)과 권한 부여(authorization)는 공존해야만 한다. 그렇지 않으면 시스템 자체의 목적에 맞게 비행기를 보호하는 꼴이 된다.
v0.2.0 필드 테스트에서는 승인할 수 없는 뷰어와 로그/추적 기록/에이전트 컨텍스트에 절대 나타나지 않는 비밀(secret)을 통해 테넌트별 예산/정책/키 격리(per-tenant budget/policy/key isolation)를 검증했다 (테스트가 확인). 전체 증거는 field-test report에서 확인할 수 있으며, 인증 부트스트랩 및 키 로테이션(key-rotation) 실행 매뉴얼은 docs/runbooks/auth-bootstrap-and-key-rotation.md에 있다.
참고 자료 (References)
감사 로그를 열어보라. 특권적인 액션 하나를 골라봐라. '누가(who)'의 정보가 인증된 주체에 의해 채워졌는가—아니면 호출자가 작성한 필드에 의해 채워졌는가? 만약 후자라면, 당신의 감사 로그는 누군가가 들려주는 이야기일 뿐이다.