<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>인가 on J2eff's Garage</title><link>https://blog.j2eff.me/tags/%EC%9D%B8%EA%B0%80/</link><description>Recent content in 인가 on J2eff's Garage</description><generator>Hugo -- gohugo.io</generator><language>ko-KR</language><lastBuildDate>Mon, 06 Jul 2026 12:00:00 +0900</lastBuildDate><atom:link href="https://blog.j2eff.me/tags/%EC%9D%B8%EA%B0%80/index.xml" rel="self" type="application/rss+xml"/><item><title>인증은 됐는데 권한이 없다 — 서명된 CreateAccount</title><link>https://blog.j2eff.me/p/%EC%9D%B8%EC%A6%9D%EC%9D%80-%EB%90%90%EB%8A%94%EB%8D%B0-%EA%B6%8C%ED%95%9C%EC%9D%B4-%EC%97%86%EB%8B%A4-%EC%84%9C%EB%AA%85%EB%90%9C-createaccount/</link><pubDate>Mon, 06 Jul 2026 12:00:00 +0900</pubDate><guid>https://blog.j2eff.me/p/%EC%9D%B8%EC%A6%9D%EC%9D%80-%EB%90%90%EB%8A%94%EB%8D%B0-%EA%B6%8C%ED%95%9C%EC%9D%B4-%EC%97%86%EB%8B%A4-%EC%84%9C%EB%AA%85%EB%90%9C-createaccount/</guid><description>&lt;!--
제목 후보 (Jeff 선택용):
 1. 인증은 됐는데 권한이 없다 — 서명된 CreateAccount ← 현재 가안
 2. 첫 AccessDenied
 3. 계정을 만드는 건 권한이다
--&gt;
&lt;p&gt;6편에서 계정을 만드는 문은 &amp;ldquo;서명 없는 admin 문&amp;quot;이었다. 박스에 접근할 수 있으면 누구나 계정을 찍었다. 7편에서 제네시스 관리 계정(&lt;code&gt;000000000001&lt;/code&gt;)을 세우고 나니, 그 문이 너무 헐겁다는 게 보인다. 진짜 AWS에서 계정 생성은 아무 문이 아니다 — &lt;strong&gt;관리 계정의 권한&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;이번 편은 그 문을 옮긴다. 서명 없는 문은 이제 제네시스 하나에만 남기고, 멤버 계정은 &lt;strong&gt;관리 루트가 서명한 &lt;code&gt;CreateAccount&lt;/code&gt;&lt;/strong&gt; 로만 태어나게 한다. 그리고 그 문 앞에서, 이 시리즈에서 처음으로 &lt;strong&gt;&amp;ldquo;인증은 됐는데 권한이 없다&amp;rdquo;&lt;/strong&gt; 는 대답을 만난다.&lt;/p&gt;
&lt;h2 id="서명되는-계정-생성"&gt;서명되는 계정 생성
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;admin create-account&lt;/code&gt;(서명 없음)를 은퇴시키고, 새 서명 서비스에 &lt;code&gt;CreateAccount&lt;/code&gt;를 둔다. 요청을 받아 처음 하는 일은 지금까지의 모든 서비스와 똑같다 — 서명 검증.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;cred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// serviceName = &amp;#34;mincloud&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;writeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authErr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authErr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;authErr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;서명이 통과하면 우리는 &lt;strong&gt;호출자가 누구인지&lt;/strong&gt; 안다. 4편의 그 골격이 여기서도 공짜로 돌아간다. 하지만 이번엔 그것만으로 부족하다.&lt;/p&gt;
&lt;h2 id="인증-다음에-오는-질문"&gt;인증 다음에 오는 질문
&lt;/h2&gt;&lt;p&gt;지금까지 mincloud는 &amp;ldquo;넌 누구냐&amp;quot;에 답하면 뭐든 통과시켰다. &lt;code&gt;CreateAccount&lt;/code&gt;는 다르다. &lt;strong&gt;누구냐&lt;/strong&gt;를 알아낸 뒤, 한 걸음 더 나가 묻는다 — &lt;strong&gt;넌 이걸 해도 되냐?&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;createAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;credstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;credstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isManagementRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;writeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;StatusForbidden&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;AccessDenied&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;User: %s is not authorized to perform: mincloud:CreateAccount&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARN&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... 멤버 계정 + 루트 발급 ...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;isManagementRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;credstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;managementAccountID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasSuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;:root&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;이 열 줄이 이 시리즈의 새로운 축이다. &lt;code&gt;Authenticate&lt;/code&gt;는 &lt;strong&gt;인증(authentication)&lt;/strong&gt; — &amp;ldquo;이 서명이 이 신원의 것이 맞나&amp;rdquo;. &lt;code&gt;isManagementRoot&lt;/code&gt;는 &lt;strong&gt;인가(authorization)&lt;/strong&gt; — &amp;ldquo;이 신원이 이 행동을 해도 되나&amp;rdquo;. 둘은 다르고, 여기서 처음으로 갈라진다.&lt;/p&gt;
&lt;h2 id="aws-cli가-모르는-명령-가르치기"&gt;aws CLI가 모르는 명령 가르치기
&lt;/h2&gt;&lt;p&gt;문제가 하나 있다. &lt;code&gt;mincloud:CreateAccount&lt;/code&gt;는 AWS에 없는 우리만의 오퍼레이션이다 (게다가 진짜 AWS의 &lt;code&gt;organizations:CreateAccount&lt;/code&gt;와 달리 루트 자격증명을 돌려준다 — 학생한테 넘겨야 하니까). aws CLI가 이 명령을 알 리 없다.&lt;/p&gt;
&lt;p&gt;그런데 aws CLI는 하드코딩된 명령이 없다. &lt;strong&gt;전부 JSON 서비스 모델에서 런타임에 생성&lt;/strong&gt;된다. 그래서 우리 모델을 하나 만들어 설치하면 된다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ aws configure add-model \
 --service-model file://models/mincloud/service-2.json \
 --service-name mincloud

$ aws mincloud help | grep create-account
 o create-account
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이제 진짜 &lt;code&gt;aws mincloud create-account&lt;/code&gt;가 생겼다. 우리가 짠 서버에, 마스터 키로 SigV4 서명해서 요청을 보내는 일급 CLI 명령이다.&lt;/p&gt;
&lt;h2 id="문-앞에서"&gt;문 앞에서
&lt;/h2&gt;&lt;p&gt;관리 루트(마스터)로 문을 두드리면, 새 계정이 나온다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ aws mincloud create-account --endpoint-url http://localhost:9930
{
 &amp;#34;Account&amp;#34;: {
 &amp;#34;AccountId&amp;#34;: &amp;#34;955722713203&amp;#34;,
 &amp;#34;RootAccessKeyId&amp;#34;: &amp;#34;AKIAN5BMDJBZ3I5VMDXG&amp;#34;,
 &amp;#34;RootSecretAccessKey&amp;#34;: &amp;#34;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&amp;#34;
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(가짜 값이다.) 이 계정의 루트 자격증명을 그대로 손에 쥐었다. 이제 &lt;strong&gt;바로 그 멤버 루트로&lt;/strong&gt; 같은 문을 두드려 보자 — 저도 루트니까 되지 않을까?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ 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
&lt;/code&gt;&lt;/pre&gt;&lt;!-- screenshot: 멤버 루트가 AccessDenied 로 거부되는 터미널 --&gt;
&lt;p&gt;&lt;strong&gt;AccessDenied.&lt;/strong&gt; AWS를 배우다 보면 누구나 한 번쯤 노려봤을 그 문장이, 이제 우리 서버에서 나온다. 중요한 건, 이게 서명이 틀려서가 아니라는 것이다. 같은 멤버 루트로 STS에 물어보면, 신원은 멀쩡히 돌아온다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ ...(같은 멤버 키로)... aws sts get-caller-identity --endpoint-url http://localhost:9900
{
 &amp;#34;UserId&amp;#34;: &amp;#34;955722713203&amp;#34;,
 &amp;#34;Account&amp;#34;: &amp;#34;955722713203&amp;#34;,
 &amp;#34;Arn&amp;#34;: &amp;#34;arn:aws:iam::955722713203:root&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;인증은 &lt;strong&gt;통과했다.&lt;/strong&gt; 서버는 이 요청이 955722713203의 루트임을 확실히 안다. 다만 그 루트가 &lt;code&gt;mincloud:CreateAccount&lt;/code&gt;를 &lt;strong&gt;해도 되는 사람이 아닐&lt;/strong&gt; 뿐이다. 문지기가 신분증은 확인했고(인증), 그다음 &amp;ldquo;당신은 이 방에 들어올 명단에 없습니다&amp;rdquo;(인가)라고 한 것이다.&lt;/p&gt;
&lt;h2 id="아직은-한-줄짜리-정책"&gt;아직은 한 줄짜리 정책
&lt;/h2&gt;&lt;p&gt;정직하게 말하면, 지금의 인가는 정책 엔진이 아니다. &lt;code&gt;isManagementRoot&lt;/code&gt; 한 줄, &amp;ldquo;관리 계정의 루트인가?&amp;ldquo;라는 단일 하드코딩 규칙이다. 진짜 AWS는 이걸 &lt;strong&gt;정책&lt;/strong&gt;으로 한다 — 누가, 어떤 행동을, 어떤 리소스에, 허용/거부인지를 데이터로 기술하고 평가한다(IAM 정책, SCP). &amp;ldquo;friend01은 인스턴스를 띄울 순 있지만 끌 순 없다&amp;rdquo; 같은 규칙을 코드 수정 없이 붙이고 떼는 게 그 세계다.&lt;/p&gt;
&lt;p&gt;또 하나. 우리 &lt;code&gt;CreateAccount&lt;/code&gt;는 새 계정의 &lt;strong&gt;루트 키를 그대로 돌려준다.&lt;/strong&gt; 편하지만, AWS가 실제로 하는 안전한 방식은 아니다. 진짜 AWS는 루트 키를 넘기지 않는다 — 멤버 계정에 역할을 심어두고, 관리 계정이 &lt;strong&gt;AssumeRole로 임시 자격증명&lt;/strong&gt;을 받아 들어간다. 장기 루트 키를 손에 쥐는 대신, 필요할 때 짧게 빌려 쓰고 버리는 것이다.&lt;/p&gt;
&lt;p&gt;그래서 앞으로 두 갈래가 열린다. 하나는 이 &lt;strong&gt;한 줄짜리 게이트를 진짜 정책 엔진으로&lt;/strong&gt; 키우는 길(인가), 다른 하나는 루트 키를 넘기는 대신 &lt;strong&gt;임시 자격증명(AssumeRole)&lt;/strong&gt; 으로 가는 길(더 안전한 접근). 둘 다, 오늘 문 앞에서 처음 마주친 이 &lt;code&gt;AccessDenied&lt;/code&gt;에서 시작된다.&lt;/p&gt;</description></item></channel></rss>