맨 아래 거북이 — 제네시스 계정과 신뢰의 뿌리

Cloud Control Plane
  1. 1. AWS CLI는 무엇을 보내는가
  2. 2. SigV4 검증기를 Go로 밑바닥부터
  3. 3. CLI가 속아 넘어가는 순간
  4. 4. IAM과 STS를 나누다
  5. 5. credstore를 프로세스 밖으로 — DynamoDB에 얹다
  6. 6. 최초의 계정은 어디서 오는가
  7. 7. 맨 아래 거북이 — 제네시스 계정과 신뢰의 뿌리
  8. 8. 인증은 됐는데 권한이 없다 — 서명된 CreateAccount
  9. 9. 루트키를 넘기지 마라 — 역할과 AssumeRole
전체 9편
7 / 9

6편에서 슬쩍 넘어간 게 있다. “씨앗 계정"이라 부르며 “하드코딩된 특별한 존재"라고만 하고 지나갔다. 이번 편은 그 씨앗의 정체를 밝히고, 거기서 시작된 질문을 끝까지 따라간다. 최초의 루트는 대체 누가 만드는가?

씨앗의 진짜 이름

씨앗은 아무 계정이 아니었다. 그건 제네시스 계정 — 모든 것이 거기서 파생되는 최초의 관리 계정이다. mincloud에서 그 번호를 000000000001로 못박았다. 지금까지 user/jeff 흉내를 내던 개발 자격증명을, 진짜 정체인 루트로 되돌린다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 제네시스 '관리 계정'과 그 루트를 심는다 — 어떤 서명된 요청보다도
// 먼저 존재해야 하는 단 하나의 계정. AWS 가입 순간 루트가 존재하는 것과 같다.
func loadManagementRoot(store credstore.Store) string {
	account := cmp.Or(os.Getenv("MINCLOUD_MANAGEMENT_ACCOUNT_ID"), "000000000001")
	store.Put(accessKeyID, credstore.Credential{
		SecretAccessKey: secret,
		Identity: credstore.Identity{
			Account: account,
			UserID:  account, // 루트의 UserId는 계정 ID
			ARN:     "arn:aws:iam::" + account + ":root",
		},
	})
	return accessKeyID
}

이제 마스터 키로 신원을 물으면, 관리 계정의 루트가 돌아온다.

$ aws sts get-caller-identity --endpoint-url http://localhost:9900
{
    "UserId": "000000000001",
    "Account": "000000000001",
    "Arn": "arn:aws:iam::000000000001:root"
}

이게 AWS의 관리 계정(management account) 에 대응한다. 조직의 꼭대기 계정, 나머지 모든 계정이 그 아래에서 태어나는 뿌리다.

그런데 그 루트는 누가 만들었나

여기서 6편의 순환이 한 층 더 깊어진다. 계정을 만들려면 계정이 있어야 한다고 했다. 그래서 부트스트랩은 서명 밖에 있어야 한다고. 좋다. 그런데 그 최초의 관리 계정 루트 자신은 누가 만들었는가? 그것도 서명 밖에서 만들어져야 하는데, 그럼 그걸 만든 권한은 어디서 왔는가?

무한히 위로 올라갈 것 같지만, 그렇지 않다. 바닥은 다른 ‘키’가 아니다. 거북이 탑은 물리적·조직적 통제에서 끝난다.

mincloud의 경우, 제네시스 루트를 심는 건 loadManagementRoot다. 그럼 그걸 실행할 권한은? 바이너리를 실행할 수 있는 사람 — 이 박스를 통제하는 사람이다. 서명도 자격증명도 아니다. “기계에 손을 댈 수 있는가"가 최종 신뢰의 뿌리다.

AWS도 원리는 같다. IAM·계정 시스템 자체가 AWS가 운영하는 서버 위의 소프트웨어고, 그 최초 상태는 API가 아니라 인프라 프로비저닝으로 심긴다. 신뢰의 뿌리는 암호가 아니라 “데이터센터와 하드웨어를 물리적으로 소유·통제한다” 는 사실이다. 소프트웨어 가 아니라 그 아래에서.

뿌리를 지키는 의식

그 최종 루트를 어떻게 안전하게 다루는가 — 여기가 진짜 정교하다. 원리는 은행이든 DNS든 CA든 똑같다.

  • HSM: 최상위 키가 탐침 방지 하드웨어 안에서 태어나 밖으로 나오지 않는다. 소프트웨어가 키를 ‘가지지’ 않는다.
  • 에어갭: 루트 키는 네트워크에서 떼어 둔다.
  • M-of-N 분할 통제: 한 사람이 루트를 쓸 수 없다. n명 중 k명이 각자의 조각(스마트카드)을 모아야 작동한다.
  • 키 세리머니: 최상위 키를 쓰는 건 다인원·기록·촬영·감사되는 의식이다.
  • 거의 쓰지 않는다: 루트는 아래 계층에 위임하고 잠가둔다. 일상 작업엔 절대 나오지 않는다.

이게 추상이 아니다. DNSSEC 루트 키 세리머니CA 루트 키 세리머니를 찾아보면, 인류가 “위에 아무것도 없는 뿌리"를 만들고 지키는 방식이 문자 그대로 이렇다 — HSM, 다인원, 에어갭, 의식. (AWS 내부의 정확한 절차는 비공개지만, 이 산업 원리를 따른다.)

mincloud가 진짜 서비스가 된다면

지금 mincloud의 뿌리는 “로컬에서 바이너리를 실행하는 나"다. 이게 진짜 호스팅 클라우드가 되면, 그 뿌리는 사라지지 않고 옮겨간다 — “프로덕션 인프라와 시크릿 저장소를 통제하는 자"에게로.

그때 반드시 바뀌는 것들:

  • 하드코딩 마스터 금지. 지금의 well-known 기본값(MINCLOUDTESTKEY0000A)은 로컬 편의일 뿐, 공개되면 백도어다. 프로덕션은 프로비저닝 때 유일한 루트를 생성해 시크릿 매니저(KMS/Vault)에 넣는다. mincloud엔 이미 그 주입구가 있다 —

    MINCLOUD_ROOT_ACCESS_KEY_ID / MINCLOUD_ROOT_SECRET_ACCESS_KEY
    

    이미지엔 절대 안 넣고, 배포 시 시크릿으로만 주입한다.

  • 부트스트랩 문은 인터넷에 노출하지 않는다. 서명 없는 동작은 운영자 전용. 세상을 마주하는 건 서명된 API뿐이다.

  • 시크릿 평문 저장 금지. 지금 DynamoDB에 시크릿이 평문으로 들어가는 건 mock이라 넘어가는 것이고, 진짜라면 해시/KMS 암호화다.

그리고 stakes가 오를수록 뿌리를 더 조인다. 작은 서비스면 “시크릿 매니저 + MFA + 감사"로 충분하고, 은행급이면 아까의 HSM+세리머니까지 간다. 요는, 진짜 서비스가 된다고 부트스트랩을 벗어나는 게 아니라, “누가 인프라를 통제하는가"를 더 형식화하고 강화할 뿐이라는 것이다.

다음: 이제 누가 계정을 만드는가

제네시스 루트가 섰다. 그럼 6편에서 아무나 열던 그 “계정 만드는 문"을 다시 볼 수 있다. 진짜 AWS에서 계정 생성은 아무 문이 아니다 — 관리 계정의 권한이다. 관리 계정이 서명해서, 그 아래 멤버 계정을 찍는다.

다음 편에서 그걸 만든다. 서명 없는 문은 이제 제네시스 하나에만 남기고, 나머지 계정은 관리 루트가 서명한 CreateAccount 로만 태어나게 한다. 그리고 그 문 앞에서, 이 시리즈에서 처음으로 “인증은 됐는데 권한이 없다” 는 대답을 만나게 된다.