6편에서 계정을 만드는 문은 “서명 없는 admin 문"이었다. 박스에 접근할 수 있으면 누구나 계정을 찍었다. 7편에서 제네시스 관리 계정(000000000001)을 세우고 나니, 그 문이 너무 헐겁다는 게 보인다. 진짜 AWS에서 계정 생성은 아무 문이 아니다 — 관리 계정의 권한이다.
이번 편은 그 문을 옮긴다. 서명 없는 문은 이제 제네시스 하나에만 남기고, 멤버 계정은 관리 루트가 서명한 CreateAccount 로만 태어나게 한다. 그리고 그 문 앞에서, 이 시리즈에서 처음으로 “인증은 됐는데 권한이 없다” 는 대답을 만난다.
서명되는 계정 생성
admin create-account(서명 없음)를 은퇴시키고, 새 서명 서비스에 CreateAccount를 둔다. 요청을 받아 처음 하는 일은 지금까지의 모든 서비스와 똑같다 — 서명 검증.
| |
서명이 통과하면 우리는 호출자가 누구인지 안다. 4편의 그 골격이 여기서도 공짜로 돌아간다. 하지만 이번엔 그것만으로 부족하다.
인증 다음에 오는 질문
지금까지 mincloud는 “넌 누구냐"에 답하면 뭐든 통과시켰다. CreateAccount는 다르다. 누구냐를 알아낸 뒤, 한 걸음 더 나가 묻는다 — 넌 이걸 해도 되냐?
| |
이 열 줄이 이 시리즈의 새로운 축이다. Authenticate는 인증(authentication) — “이 서명이 이 신원의 것이 맞나”. isManagementRoot는 인가(authorization) — “이 신원이 이 행동을 해도 되나”. 둘은 다르고, 여기서 처음으로 갈라진다.
aws CLI가 모르는 명령 가르치기
문제가 하나 있다. mincloud:CreateAccount는 AWS에 없는 우리만의 오퍼레이션이다 (게다가 진짜 AWS의 organizations:CreateAccount와 달리 루트 자격증명을 돌려준다 — 학생한테 넘겨야 하니까). aws CLI가 이 명령을 알 리 없다.
그런데 aws CLI는 하드코딩된 명령이 없다. 전부 JSON 서비스 모델에서 런타임에 생성된다. 그래서 우리 모델을 하나 만들어 설치하면 된다.
$ aws configure add-model \
--service-model file://models/mincloud/service-2.json \
--service-name mincloud
$ aws mincloud help | grep create-account
o create-account
이제 진짜 aws mincloud create-account가 생겼다. 우리가 짠 서버에, 마스터 키로 SigV4 서명해서 요청을 보내는 일급 CLI 명령이다.
문 앞에서
관리 루트(마스터)로 문을 두드리면, 새 계정이 나온다.
$ aws mincloud create-account --endpoint-url http://localhost:9930
{
"Account": {
"AccountId": "955722713203",
"RootAccessKeyId": "AKIAN5BMDJBZ3I5VMDXG",
"RootSecretAccessKey": "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"
}
}
(가짜 값이다.) 이 계정의 루트 자격증명을 그대로 손에 쥐었다. 이제 바로 그 멤버 루트로 같은 문을 두드려 보자 — 저도 루트니까 되지 않을까?
$ AWS_ACCESS_KEY_ID=AKIAN5BMDJBZ3I5VMDXG \
AWS_SECRET_ACCESS_KEY=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 \
aws mincloud create-account --endpoint-url http://localhost:9930
An error occurred (AccessDenied) when calling the CreateAccount
operation: User: arn:aws:iam::955722713203:root is not authorized
to perform: mincloud:CreateAccount
AccessDenied. AWS를 배우다 보면 누구나 한 번쯤 노려봤을 그 문장이, 이제 우리 서버에서 나온다. 중요한 건, 이게 서명이 틀려서가 아니라는 것이다. 같은 멤버 루트로 STS에 물어보면, 신원은 멀쩡히 돌아온다.
$ ...(같은 멤버 키로)... aws sts get-caller-identity --endpoint-url http://localhost:9900
{
"UserId": "955722713203",
"Account": "955722713203",
"Arn": "arn:aws:iam::955722713203:root"
}
인증은 통과했다. 서버는 이 요청이 955722713203의 루트임을 확실히 안다. 다만 그 루트가 mincloud:CreateAccount를 해도 되는 사람이 아닐 뿐이다. 문지기가 신분증은 확인했고(인증), 그다음 “당신은 이 방에 들어올 명단에 없습니다”(인가)라고 한 것이다.
아직은 한 줄짜리 정책
정직하게 말하면, 지금의 인가는 정책 엔진이 아니다. isManagementRoot 한 줄, “관리 계정의 루트인가?“라는 단일 하드코딩 규칙이다. 진짜 AWS는 이걸 정책으로 한다 — 누가, 어떤 행동을, 어떤 리소스에, 허용/거부인지를 데이터로 기술하고 평가한다(IAM 정책, SCP). “friend01은 인스턴스를 띄울 순 있지만 끌 순 없다” 같은 규칙을 코드 수정 없이 붙이고 떼는 게 그 세계다.
또 하나. 우리 CreateAccount는 새 계정의 루트 키를 그대로 돌려준다. 편하지만, AWS가 실제로 하는 안전한 방식은 아니다. 진짜 AWS는 루트 키를 넘기지 않는다 — 멤버 계정에 역할을 심어두고, 관리 계정이 AssumeRole로 임시 자격증명을 받아 들어간다. 장기 루트 키를 손에 쥐는 대신, 필요할 때 짧게 빌려 쓰고 버리는 것이다.
그래서 앞으로 두 갈래가 열린다. 하나는 이 한 줄짜리 게이트를 진짜 정책 엔진으로 키우는 길(인가), 다른 하나는 루트 키를 넘기는 대신 임시 자격증명(AssumeRole) 으로 가는 길(더 안전한 접근). 둘 다, 오늘 문 앞에서 처음 마주친 이 AccessDenied에서 시작된다.