<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>IAM on J2eff's Garage</title><link>https://blog.j2eff.me/tags/iam/</link><description>Recent content in IAM on J2eff's Garage</description><generator>Hugo -- gohugo.io</generator><language>ko-KR</language><lastBuildDate>Sun, 05 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.j2eff.me/tags/iam/index.xml" rel="self" type="application/rss+xml"/><item><title>IAM과 STS를 나누다</title><link>https://blog.j2eff.me/p/iam%EA%B3%BC-sts%EB%A5%BC-%EB%82%98%EB%88%84%EB%8B%A4/</link><pubDate>Sun, 05 Jul 2026 00:00:00 +0000</pubDate><guid>https://blog.j2eff.me/p/iam%EA%B3%BC-sts%EB%A5%BC-%EB%82%98%EB%88%84%EB%8B%A4/</guid><description>&lt;!--
제목 후보 (Jeff 선택용):
 1. IAM과 STS를 나누다 ← 현재 가안
 2. 키를 발급하는 서비스와 검증하는 서비스
 3. 한 프로세스, 두 엔드포인트 — 서비스 경계를 긋다
--&gt;
&lt;p&gt;3편 끝에서 한 가지를 짚어뒀다. 지금까지 우리가 만든 건 STS 하나였지만, 진짜 AWS에서 IAM과 STS는 애초에 다른 서비스라는 것. 엔드포인트부터 다르다. IAM은 &lt;code&gt;iam.amazonaws.com&lt;/code&gt; 하나로 글로벌이고, STS는 &lt;code&gt;sts.ap-northeast-2.amazonaws.com&lt;/code&gt;처럼 리전마다 주소가 갈린다. 자격증명을 &lt;strong&gt;발급&lt;/strong&gt;하는 일과 자격증명을 &lt;strong&gt;검증&lt;/strong&gt;하는 일이 서로 다른 곳에서 벌어진다는 뜻이다.&lt;/p&gt;
&lt;p&gt;이번 편은 그 경계를 mincloud 안에 실제로 긋는다. 지금까지 STS 핸들러 하나가 하던 일을, IAM과 STS 두 개의 독립 서비스로 쪼갠다. 그리고 그 과정에서, 무심코 지나치면 인증이 통째로 뚫리는 함정 하나를 만난다.&lt;/p&gt;
&lt;h2 id="왜-나누나"&gt;왜 나누나
&lt;/h2&gt;&lt;p&gt;역할이 다르기 때문이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IAM은 컨트롤플레인이다.&lt;/strong&gt; 사용자를 만들고, 정책을 붙이고, 액세스키를 &lt;strong&gt;발급&lt;/strong&gt;한다. &amp;ldquo;누가 이 계정에서 무엇을 할 수 있는가&amp;quot;를 정의하는 쪽이다. 계정 전체에 하나뿐인 진실이라, 진짜 AWS에서도 리전을 나누지 않고 글로벌 엔드포인트 하나로 굴린다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;STS는 그 자격증명을 검증하고 신원을 돌려주는 쪽이다.&lt;/strong&gt; &lt;code&gt;GetCallerIdentity&lt;/code&gt;가 그 예다. 요청이 실제로 리소스를 때리는 데이터플레인 가까이에 있어야 하니, 리전별로 엔드포인트가 흩어져 있다.&lt;/p&gt;
&lt;p&gt;1편에서 봤던 SigV4 &lt;code&gt;Credential&lt;/code&gt; 범위를 다시 떠올려 보자. &lt;code&gt;.../ap-northeast-2/sts/aws4_request&lt;/code&gt; — 날짜, 리전, 그리고 &lt;strong&gt;서비스 이름&lt;/strong&gt;이 서명 범위에 박혀 있었다. AWS가 서비스를 이렇게 나눠 뒀기 때문에, 서명 자체가 &amp;ldquo;이 요청은 sts용이다&amp;quot;라고 자기 자신을 못 박는다. 우리가 이 구조를 흉내 내려면, 서버 쪽도 서비스별로 갈라야 앞뒤가 맞는다.&lt;/p&gt;
&lt;h2 id="한-프로세스-두-리스너"&gt;한 프로세스, 두 리스너
&lt;/h2&gt;&lt;p&gt;경계를 긋는다고 프로세스까지 둘로 쪼갤 필요는 없다. mincloud는 여전히 바이너리 하나다. 대신 서비스마다 독립된 &lt;code&gt;http.Handler&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;span class="lnt"&gt;6
&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;store&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;credstore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;New&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;errc&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="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;chan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&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 class="nx"&gt;errc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&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="nf"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stsLn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;sts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handler&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="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;verbose&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="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&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 class="nx"&gt;errc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&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="nf"&gt;Serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iamLn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Handler&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="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;verbose&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="nx"&gt;errc&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;/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;sts.Handler&lt;/code&gt;와 &lt;code&gt;iam.Handler&lt;/code&gt;는 각자 자기 패키지에 있고, 자기가 아는 &lt;code&gt;Action&lt;/code&gt;만 처리한다. STS는 &lt;code&gt;GetCallerIdentity&lt;/code&gt;, IAM은 &lt;code&gt;CreateAccessKey&lt;/code&gt;. 모르는 액션엔 &lt;code&gt;InvalidAction&lt;/code&gt;을 돌려준다.&lt;/p&gt;
&lt;p&gt;포인트는 두 핸들러가 &lt;strong&gt;같은 &lt;code&gt;store&lt;/code&gt;를 공유한다&lt;/strong&gt;는 것이다. IAM이 발급한 키를 STS가 검증하려면, 둘이 같은 자격증명 저장소를 봐야 한다. 지금은 프로세스가 하나라 그냥 인메모리 맵 하나를 두 goroutine이 나눠 쓰면 된다 — 그래서 &lt;code&gt;credstore.Store&lt;/code&gt;는 &lt;code&gt;sync.RWMutex&lt;/code&gt;로 동시 접근에 안전하게 만들어 뒀다.&lt;/p&gt;
&lt;p&gt;인증 로직은 공통이라 &lt;code&gt;service.Authenticate&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;/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&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;serviceName&lt;/code&gt;. &lt;code&gt;iam&lt;/code&gt; 패키지는 &lt;code&gt;&amp;quot;iam&amp;quot;&lt;/code&gt;을, &lt;code&gt;sts&lt;/code&gt; 패키지는 &lt;code&gt;&amp;quot;sts&amp;quot;&lt;/code&gt;를 넘긴다. 별것 아닌 문자열 같지만, 이 인자가 이번 편의 핵심이다.&lt;/p&gt;
&lt;h2 id="함정-서명은-스스로를-검증하지-않는다"&gt;함정: 서명은 스스로를 검증하지 않는다
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;Authenticate&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;/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="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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;sigv4&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Verify&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;auth&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;cred&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecretAccessKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&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="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;AuthError&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="s"&gt;&amp;#34;SignatureDoesNotMatch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="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;Verify&lt;/code&gt;는 클라이언트가 했을 서명 계산을 그대로 재현해서, 결과가 요청에 실린 서명과 같은지 본다. 그런데 그 계산에 들어가는 서명 키(signing key)가 어떻게 만들어지는지 보자.&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;/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;key&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="nf"&gt;signingKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Service&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;/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;auth.Service&lt;/code&gt; — 이건 우리가 아는 서비스 이름이 아니다. &lt;strong&gt;요청의 &lt;code&gt;Authorization&lt;/code&gt; 헤더에서 파싱해 온, 클라이언트가 적어 넣은 값&lt;/strong&gt;이다. 서명 키가 이 값으로 파생된다는 건, 클라이언트가 서명할 때 쓴 서비스 이름과 서버가 검증할 때 쓰는 서비스 이름이 &lt;strong&gt;항상 같은 출처&lt;/strong&gt;라는 뜻이다. 둘 다 헤더에서 온다.&lt;/p&gt;
&lt;p&gt;그래서 이런 일이 벌어진다. 어떤 클라이언트가 서비스를 &lt;code&gt;iam&lt;/code&gt;으로 서명해 요청을 만든다. 이 요청을 STS 핸들러에 던진다. STS가 &lt;code&gt;Verify&lt;/code&gt;를 부르면, 헤더에 적힌 &lt;code&gt;iam&lt;/code&gt;으로 키를 파생해 서명을 다시 계산한다. 클라이언트도 &lt;code&gt;iam&lt;/code&gt;으로 서명했으니 — &lt;strong&gt;당연히 맞는다.&lt;/strong&gt; 서명은 자기가 어느 서비스로 갔어야 하는지 모른다. 그저 헤더가 시키는 대로 자기 자신과 아귀만 맞을 뿐이다.&lt;/p&gt;
&lt;p&gt;즉 &lt;code&gt;Verify&lt;/code&gt; 하나만으로는, iam으로 서명한 요청이 STS 문으로 들어와도 통과해 버린다. 서명 검증은 &amp;ldquo;이 서명이 이 헤더 내용과 일치하는가&amp;quot;만 볼 뿐, &amp;ldquo;이 요청이 이 서비스에 와도 되는가&amp;quot;는 보지 않는다.&lt;/p&gt;
&lt;p&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;/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="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Service&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;serviceName&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="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;AuthError&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="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;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="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="s"&gt;&amp;#34;SignatureDoesNotMatch&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="w"&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 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="s"&gt;&amp;#34;Credential should be scoped to correct service: &amp;#39;%s&amp;#39;.&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;serviceName&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="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;serviceName&lt;/code&gt;은 클라이언트가 아니라 &lt;strong&gt;핸들러 자신이 아는 값&lt;/strong&gt;이다. STS 핸들러는 &lt;code&gt;&amp;quot;sts&amp;quot;&lt;/code&gt;를 넘겼으니, 헤더의 &lt;code&gt;auth.Service&lt;/code&gt;가 &lt;code&gt;iam&lt;/code&gt;이면 여기서 걸린다. 이게 진짜 AWS가 서비스를 엔드포인트로 갈라 두는 것의 서버 쪽 대응이다.&lt;/p&gt;
&lt;p&gt;실제로 확인해 보자. &lt;code&gt;aws iam&lt;/code&gt; 명령은 서비스 &lt;code&gt;iam&lt;/code&gt;으로 서명하는데, 이걸 STS 포트(:19900)로 보내면:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ aws iam create-access-key --user-name friend01 \
 --endpoint-url http://localhost:19900

An error occurred (SignatureDoesNotMatch) when calling the
CreateAccessKey operation: Credential should be scoped to correct
service: &amp;#39;sts&amp;#39;.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;반대로 &lt;code&gt;aws sts&lt;/code&gt; 명령을 IAM 포트(:19910)로 보내면:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ aws sts get-caller-identity --endpoint-url http://localhost:19910

An error occurred (SignatureDoesNotMatch) when calling the
GetCallerIdentity operation: Credential should be scoped to correct
service: &amp;#39;iam&amp;#39;.
&lt;/code&gt;&lt;/pre&gt;&lt;!-- screenshot: 두 개의 크로스-포트 요청이 각각 'correct service' 메시지로 거부되는 터미널 --&gt;
&lt;p&gt;서명은 멀쩡한데 서비스 범위가 안 맞아 거부된다. 이 한 줄짜리 체크가 없었다면, 두 서비스를 나눈 게 이름뿐이고 문은 사실상 하나였을 것이다.&lt;/p&gt;
&lt;h2 id="키의-일생-createaccesskey"&gt;키의 일생: CreateAccessKey
&lt;/h2&gt;&lt;p&gt;이제 IAM이 실제로 하는 일, 액세스키 발급을 보자. &lt;code&gt;CreateAccessKey&lt;/code&gt;는 &lt;code&gt;UserName&lt;/code&gt;을 받는데, 생략하면 &lt;strong&gt;호출자 본인&lt;/strong&gt;에게 키를 발급한다. 실제 AWS와 같은 동작이다.&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;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&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;createAccessKey&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&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="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="nx"&gt;userName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&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&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="nx"&gt;identity&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;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Identity&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="nx"&gt;name&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="nf"&gt;userNameFromARN&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;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;userName&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="s"&gt;&amp;#34;&amp;#34;&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;userName&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;name&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="nx"&gt;name&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 class="nx"&gt;userName&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="nx"&gt;identity&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 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&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="nx"&gt;Account&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;Account&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="nx"&gt;UserID&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;AIDA&amp;#34;&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="nf"&gt;mustRandomAlphanumeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessKeyIDLength&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="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;arn:aws:iam::&amp;#34;&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;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;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="s"&gt;&amp;#34;:user/&amp;#34;&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;userName&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="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="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="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;accessKeyID&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="s"&gt;&amp;#34;AKIA&amp;#34;&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="nf"&gt;mustRandomAlphanumeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessKeyIDLength&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="nx"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&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="nf"&gt;randomSecret&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="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessKeyID&lt;/span&gt;&lt;span class="p"&gt;,&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&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="nx"&gt;SecretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;secretAccessKey&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="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="nx"&gt;identity&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="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;// ... AccessKeyId, SecretAccessKey 를 XML 로 응답&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;발급 규칙은 진짜 AWS의 겉모습을 따랐다. 액세스키 ID는 &lt;code&gt;AKIA&lt;/code&gt; 접두사에 대문자·숫자 16글자를 붙여 만들고, 시크릿은 40글자짜리 base64다. 그리고 &lt;strong&gt;발급 즉시 &lt;code&gt;store.Put&lt;/code&gt;으로 저장&lt;/strong&gt;한다. 여기가 핵심이다 — 방금 만든 키가 credstore에 들어감으로써, 곧바로 검증 가능한 자격증명이 된다.&lt;/p&gt;
&lt;p&gt;그 &amp;ldquo;곧바로&amp;quot;를 눈으로 보자. 기본 개발 자격증명(&lt;code&gt;jeff&lt;/code&gt;)으로 &lt;code&gt;friend01&lt;/code&gt;의 키를 발급받는다. IAM 포트로 간다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ aws iam create-access-key --user-name friend01 \
 --endpoint-url http://localhost:19910
{
 &amp;#34;AccessKey&amp;#34;: {
 &amp;#34;UserName&amp;#34;: &amp;#34;friend01&amp;#34;,
 &amp;#34;AccessKeyId&amp;#34;: &amp;#34;AKIASUCX2E4BPLARBYEZ&amp;#34;,
 &amp;#34;Status&amp;#34;: &amp;#34;Active&amp;#34;,
 &amp;#34;SecretAccessKey&amp;#34;: &amp;#34;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&amp;#34;,
 &amp;#34;CreateDate&amp;#34;: &amp;#34;2026-07-05T13:54:19+00:00&amp;#34;
 }
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(여기 나온 키·시크릿은 로컬 테스트로 방금 생성된 가짜 값이다. 어디에도 통하지 않는다.)&lt;/p&gt;
&lt;p&gt;이제 &lt;strong&gt;방금 받은 그 키&lt;/strong&gt;를 들고, 이번엔 STS 포트로 가서 &amp;ldquo;나는 누구냐&amp;quot;고 묻는다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ AWS_ACCESS_KEY_ID=AKIASUCX2E4BPLARBYEZ \
 AWS_SECRET_ACCESS_KEY=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 \
 aws sts get-caller-identity --endpoint-url http://localhost:19900
{
 &amp;#34;UserId&amp;#34;: &amp;#34;AIDACNDV6C5Z2A561BOG&amp;#34;,
 &amp;#34;Account&amp;#34;: &amp;#34;123456789012&amp;#34;,
 &amp;#34;Arn&amp;#34;: &amp;#34;arn:aws:iam::123456789012:user/friend01&amp;#34;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;키의 일생이 두 엔드포인트를 넘나든다. IAM(:19910)에서 &lt;strong&gt;발급&lt;/strong&gt;돼 credstore에 &lt;strong&gt;저장&lt;/strong&gt;되고, STS(:19900)에서 &lt;strong&gt;검증&lt;/strong&gt;돼 friend01이라는 신원으로 되돌아온다. 서버 로그가 그 왕복을 그대로 찍는다.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;iam CreateAccessKey by arn:aws:iam::123456789012:user/jeff
sts GetCallerIdentity by arn:aws:iam::123456789012:user/friend01
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;jeff가 friend01의 키를 발급했고, 그 키가 STS에서 friend01로 인증됐다. 두 서비스가 하나의 저장소를 매개로 대화한 것이다.&lt;/p&gt;
&lt;h2 id="지금은-글로벌이-공짜다"&gt;지금은 &amp;ldquo;글로벌&amp;quot;이 공짜다
&lt;/h2&gt;&lt;p&gt;진짜 AWS에서 IAM이 글로벌이라는 건, 서울에서 만든 사용자가 버지니아에서도 즉시 보인다는 뜻이고, 그 뒤에는 리전 간 복제라는 만만찮은 인프라가 있다. mincloud는 그 문제를 아직 우아하게 &lt;strong&gt;회피&lt;/strong&gt;하고 있을 뿐이다. 프로세스가 하나고 credstore가 인메모리 맵 하나니, IAM이 &lt;code&gt;Put&lt;/code&gt;한 키를 STS가 즉시 &lt;code&gt;Lookup&lt;/code&gt;하는 게 당연하다. 복제도, 지연도, 불일치도 없다 — 나눌 데이터가 애초에 한 벌뿐이라서.&lt;/p&gt;
&lt;p&gt;이 공짜는 인스턴스를 여러 개로 늘리는 순간 끝난다. IAM 인스턴스 A가 발급한 키를, STS 인스턴스 B가 자기 메모리에서 못 찾는 상황이 생긴다. 그때부터 credstore를 프로세스 밖으로 빼고(공유 저장소), 복제와 eventual consistency를 마주해야 한다. &amp;ldquo;방금 만든 키가 잠깐 인식이 안 되는&amp;rdquo; 그 지연이, 사실 진짜 AWS에서도 IAM 변경이 전파되는 데 몇 초씩 걸리는 이유다. 다음 편의 주제로 남겨 둔다.&lt;/p&gt;
&lt;p&gt;정직하게 덧붙이면, 지금 IAM은 &lt;code&gt;CreateAccessKey&lt;/code&gt; 하나뿐이다. &lt;code&gt;CreateUser&lt;/code&gt;도, 사용자 디렉토리도 없다. &lt;code&gt;friend01&lt;/code&gt;은 키를 발급하는 순간 즉석에서 만들어진 신원이지, 어딘가 등록된 사용자가 아니다. &amp;ldquo;사용자를 먼저 만들고, 그 사용자에게 키를 발급한다&amp;quot;는 IAM 본연의 순서는 아직 반쪽이다. 컨트롤플레인이라고 부르기엔 이르지만, 적어도 발급과 검증이 서로 다른 문을 통해 이뤄지는 골격은 이제 섰다.&lt;/p&gt;</description></item></channel></rss>