클라우드|31 min read

[Part1 기본 개념 4편] IAM - 클라우드의 인증과 인가

IAM이 어려운 진짜 이유는 클라우드의 새 개념이라서가 아니라, Linux 권한 모델이라는 기본기가 비어 있기 때문입니다. UID·GID·9bits부터 sudo, POSIX ACL을 거쳐 AWS의 Principal, Policy, Trust, STS까지 어떻게 분산 환경으로 확장되는지 풀어봅니다.

김현욱
[Part1 기본 개념 4편]  IAM - 클라우드의 인증과 인가

들어가며: "Access Denied"

클라우드 기본 개념 시리즈의 마지막은 IAM입니다. 클라우드에서 VPC와 더불어 배우고 설계하기 가장 까다로운 파트가 IAM이라고 생각합니다. 여러 클라우드 서비스를 연결하거나 조합해서 사용해보면 거의 100%의 확률로 마주치는 메시지가 있습니다.

1An error occurred (AccessDenied) when calling the GetObject operation:
2User: arn:aws:iam::123456789012:user/wook is not authorized to perform:
3s3:GetObject on resource: "arn:aws:s3:::my-bucket/secret.json"
4because no identity-based policy allows the s3:GetObject action.

웹에서 CORS가 있다면, 클라우드에는 Access Denied 가 있습니다. 에러 메시지는 생각보다 단순합니다. "권한이 없어서 이 행동을 할 수 없다"라는 의미입니다. 만약 맥북이나 리눅스에서 위와 같은 에러가 발생했다면 시스템 권한 설정을 변경하거나 chmod 로 해당 파일에 권한을 주면 될 것입니다.

하지만 위와 같은 IAM 에러 메시지에는 arn,identity-based policy,action,resource 등 자주 접해보지 못한 단어를 한꺼번에 쏟아집니다. 이를 해결하기 위해 콘솔로 들어가면 User,Group,Role,Policy,SCP 등 IAM 메뉴에 설정해야하는 수 많은 것이 등장합니다. 그래서 대개 루트 권한을 주거나 (이러면 절대 안 됩니다!) 하나씩 추가하다가 답답해서 해당 리소스의 슈퍼 권한을 주는 주니어 엔지니어들이 대부분입니다.

"왜 IAM이 쉽게 이해가 되지 않고 사용하기 어려울까"를 오랜 시간 고민해봤습니다. 결국엔 돌고 돌아 기본기가 부족하기 때문이었다는 결론의 도달했습니다. 사실 IAM은 클라우드에서만 쓰는 완전히 새로운 개념이나 기능이 아닙니다. 그저 Linux의 권한 모델을 분산 환경과 클라우드 환경에 맞춰서 추상화하고 확장한 것 뿐입니다.

이번 포스팅에서는 Linux 권한 모델 개념부터 시작해서 IAM의 핵심인 Principal, Policy, Trust, STS 까지 설명해보겠습니다.


Linux의 권한 모델

IAM을 이해하려면 기본으로 돌아가 OS가 어떻게 권한을 다루는지 이해해야 합니다. 이번 포스팅에서는 IAM을 이해하기 위한 개념들만 다루겠습니다.

1. UID, GID 그리고 9bits 권한

터미널창에 ls -l /var/log/syslog 를 쳐보면 다음과 비슷한 출력을 확인할 수 있습니다.

1-rw-r----- 1 syslog adm 1234567 May 6 10:22 /var/log/syslog

사실 이 한 줄에 모든 Linux의 권한 모델 핵심이 다 있습니다. 각 사용자는 UID(User ID: 사용자 식별 번호)와 GID(Group ID: 그룹 식별 번호)로 식별되고, 모든 파일은 (UID, GID, 9bits 권한)의 메타데이터를 가집니다.

위치/값 의미
- (첫 글자) 파일 타입 (디렉터리면 d, 심볼릭 링크면 l)
rw- owner (소유자) 권한
r-- group (그룹) 권한
--- others (그 외 사용자) 권한
syslog 소유자 사용자명
adm 소유 그룹명

권한 모델의 핵심은 "행위자(누가)자원(무엇을)을 분리하고, 그 사이에 규칙을 둔다" 는 것입니다. Linux 권한 모델과 IAM의 구조가 정확히 같은 구조이기 때문에 기억해두시길 바랍니다.

1[행위자: User UID/GID] ──권한 규칙──▶ [자원: 파일/디렉터리]

아래의 내용은 UID, GID 와 관련된 심화 내용으로 2.sudo와 권한 위임 으로 넘어가도 글을 이해하는데 지장없습니다.

이름 뒤에 숨은 식별자 - Number ID

UID와 GID 를 보충 설명하고 넘어가겠습니다. ls -l 출력 결과를 보면 사용자, 그룹의 이름은 보이는데 식별번호가 따로 보이지 않습니다. syslogadm 은 Domain name과 비슷하게 사람이 읽기 쉽게 하기 위한 표기일 뿐입니다. 즉, 커널은 이 이름의 의미를 정확히 알지 못합니다. 커널 알고 있는 건 오직 숫자 ID입니다. ls -n 옵션으로 UID와 GID의 진짜 정체를 알 수 있습니다.

1-rw-r----- 1 104 4 1234567 May 6 10:22 /var/log/syslog

위에 출력 예시에서는 syslog104로, adm4인 것을 확인할 수 있습니다. 이름은 표시할 때 갖다 붙인 라벨일 뿐이고, 이 라벨을 Linux 에서 보통 2개의 파일에서 결정됩니다.

대상 매핑 정보 시스템 파일 경로
사용자 (User) 사용자 이름 ↔ UID (User ID) /etc/passwd
그룹 (Group) 그룹 이름 ↔ GID (Group ID) /etc/group

/etc/passwdsyslog/etc/groupadm을 확인해보면 다음과 같은 출력을 확인할 수 있습니다.

1grep '^syslog' /etc/passwd
2# 출력 예시
3syslog:x:104:111::/home/syslog:/usr/sbin/nologin
4# 출력 예시 설명
5syslog : x : 104 : 111 : : /home/syslog : /usr/sbin/nologin
6 ↑ ↑ ↑ ↑ ↑ ↑ ↑
7 이름 pw UID GID 풀네임 홈 디렉토리 로그인 셸
8
9# pw의 x 는 "비밀번호는 /etc/shadow 에 따로 저장되어 있다" 는 placeholder입니다.
10# 옛날엔 여기에 해시가 직접 있었지만, 권한 분리를 위해 분리됐습니다.
1grep '^adm' /etc/group
2
3# 출력 예시
4adm:x:4:syslog,wook
5# 출력 예시 설명
6adm : x : 4 : syslog,wook
7 ↑ ↑ ↑ ↑
8이름 pw GID 이 그룹의 멤버 사용자들
INFO

💡 노트 - 숫자와 이름을 분리한 이유

크게 2가지 이유가 있습니다.

1. 커널 데이터 구조에는 정수가 훨씬 빠르고 메모리 효율적이다. 따라서 inode 에 사용자명 문자열을 저장하면 디스크가 폭발할 수도 있다.
2. NFS 같은 분산 파일시스템은 숫자 UID로만 통신한다. "머신 A의 UID 1000 == 머신 B의 UID 1000 == 같은 사용자" 라는 규약이 있어야 NFS가 동작한다. 따라서 이 규악이 깨지면 머신 A의 철수가 만든 파일이 머신 B의 영희에게 보이는 사고납니다.
참고로 Docker 컨테이너 안의 UID가 호스트와 충돌하는 문제도 같은 이유입니다.

Linux 에 저장된 내 정보의 진짜 구성 - id

내 셸의 사용자 신원이 어떻게 구성되어 있는지는 id 명령으로 확인할 수 있습니다.

1# 현재 쉘 사용자 정보 조회
2id
3# 출력 예시
4uid=1000(wook) gid=1000(wook) groups=1000(wook),4(adm),27(sudo),100(users),999(docker)

출력 결과를 좀 더 살펴보겠습니다.

항목 의미
uid=1000(wook) 현재 사용자의 UID + 이름
gid=1000(wook) primary group GID + 이름 (이 사용자가 새로 만드는 파일의 기본 그룹)
groups=... 속한 모든 그룹 (primary + supplementary)

여기서 groups= 이 중요합니다. 사용자는 하나의 primary group 여러 개의 supplementary group 에 속할 수 있습니다. 위 예시의 wook은 자기 그룹(1000) 외에 adm(로그 읽기), sudo(sudo 사용 권한), docker(도커 데몬 접근) 그룹에도 속해 있어서 그 그룹들의 권한을 모두 추가로 받습니다.

다른 사용자의 정보도 조회 가능합니다.

1# id $사용자 이름
2id syslog
3# 출력 예시
4uid=104(syslog) gid=111(syslog) groups=111(syslog),4(adm)

syslog 사용자는 UID 104, primary group GID 111(syslog), 그리고 추가로 adm 그룹(GID 4) 에 속해 있습니다. 이 때 syslogadm에 속해 있다는 게 중요합니다. 이게 왜 중요할까요?

/var/log/syslog 파일 (-rw-r----- syslog:adm) 을 다시 봅시다. "왜 owner=syslog, group=adm 으로 만들어져 있을까?"

답은 syslog 데몬(백그라운드에서 계속 돌아가는 프로세스)이 adm 그룹에도 속해있어서, 로그 파일을 만들 때 그룹을 adm으로 지정할 수 있기 때문입니다. 그러면 adm 그룹 멤버 (예: 운영자 사용자들) 가 로그를 읽을 수 있습니다. 만약 그룹이 syslog 였다면 syslog 데몬 외에는 아무도 못 읽었을겁니다. "데몬이 자기 권한 외에 추가로 다른 그룹에 속해서 협력 채널을 만든다" 는 게 Linux 권한 모델의 흔한 패턴입니다.

INFO

💡 노트 - UID/GID 의 범위

UID/GID는 아무 숫자나 쓰는 것이 아니라 관습적으로 사용하는 범위가 있습니다.

0 : root(예약). 절대 다른 용도로 쓰지 않음
1~999(배포판마다 다름): 시스템 사용자. 주로 데몬 전용으로 로그인 못하도록 셸이 /usr/sbin/nologin으로 설정됨
1000~60000: 일반 사용자. useradd 가 새 사용자를 만들 때 이 범위에서 자동 할당됨
65534 : nobody(예약), NFS에서 "매핑 안되는 사용자"의 fallback 용

데몬마다 시스템 사용자를 따로 만드는 이유는 권한 격리 때문입니다. nginx가 root로 돌면 nginx 취약점 하나가 시스템 전체 침해로 이어지지만, nginx 전용 UID로 돌면 침해 영향이 nginx의 권한 범위로 한정됩니다.

이 "각 서비스에 최소 권한의 전용 신원을 주자" 사상이 IAM의 Service Role 로 그대로 옮겨갑니다 — Lambda는 LambdaExecutionRole, ECS task는 TaskRole, EC2는 InstanceProfile 등마치 "각 데몬에게 전용 시스템 UID를 만들어주는" OS의 관습이 클라우드로 그대로 이주한 것입니다.

숫자로 확인할 수 있는 9비트 권한

-rw-r----- 같은 표기는 사람이 읽기는 좋지만, 사실 9bits 로 이루어져 있습니다.

1- rw- r-- ---
2 ↓↓↓ ↓↓↓ ↓↓↓
3 110 100 000 ← 이진수
4 6 4 0 ← 8진수 (각 그룹 = read 4 + write 2 + execute 1)

그래서 chmod 640 file 은 chmod u=rw,g=r,o(other)=권한없음 file 과 같은 의미입니다. 자주는 9bits 조합은 다음과 같습니다.

WARNING

⚠️ 주의

chmod 777 은 "나는 권한 모델을 안 쓰겠다" 는 선언과 같습니다. 누구나 그 파일을 수정할 수 있고, 누구나 (실행 파일이라면) 자기 권한으로 실행시켜 권한 상승까지 노릴 수 있어요. "안 되네 그냥 777 줘버리자" 는 보안 사고로 가는 가장 흔한 길입니다. AWS의 s3:PutObjectAcl 로 버킷을 public-read-write로 만드는 것과 같은 안티패턴이에요

INFO

💡 노트 - 디렉토리에서 x(실행권한) 의 의미

파일과 디렉토리의 x(실행권한) 의미가 살짝 다릅니다. 파일에서는 프로그램 실행을 의미하지만 디렉토리에서는 디렉토리 안으로 들어갈 수 있음, 안의 파일에 접근할 수 있음의 의미입니다.

즉, 디렉토리에 x가 주어지지않으면 그 안의 파일에 절대 접근할 수 없습니다. "분명히 644인데 왜 파일을 못 읽지?"의 흔한 원인 중 하나로 대개 부모 디렉토리의 x비트가 빠진 경우에 발생합니다.

프로세스도 UID를 갖는다.

OS에서 UID를 갖는 또 하나의 주체가 있습니다. 바로 "프로세스"입니다. 모든 프로세스는 자기를 시작한 사용자의 UID로 실행되고, 그 UID로 자원(파일 등)에 접근합니다.

1ps -eo pid,user,uid,gid,comm | head
2
3# 출력 예시
4 PID USER UID GID COMMAND
5 1 root 0 0 systemd
6 123 syslog 104 111 rsyslogd
7 456 postgres 115 122 postgres
8 789 wook 1000 1000 zsh

PID 789의 zsh 가 /var/log/syslog (owner=syslog, group=adm, mode=0640) 를 읽으려고 하면 커널이 다음과 같이 판단합니다. 이 플로우가 "파일에 권한이 있는지 어떻게 판단하는가"의 실제 내부 동작입낟.

1질문: UID=1000(wook) 이 syslog:adm 0640 파일을 읽을 수 있나?
2
3 1. owner와 일치?
4 UID 1000 ≠ 104(syslog) → NO → owner 비트 무시
5
6 2. 같은 그룹?
7 primary GID 1000 ≠ 4(adm)
8 → 하지만 wook 의 supplementary groups에 4(adm) 포함됨
9 → YES → group 비트 검사
10
11 3. group 비트에 r 켜짐? (640 의 가운데 4 = r--)
12 → YES
13
14 → 허용
INFO

💡 노트: 프로세스의 3가지 UID

사실 한 프로세스는 UID를 세 종류 갖습니다. real UID (이 프로세스를 시작한 사람), effective UID (권한 체크에 실제로 쓰는 ID), saved UID (잠시 권한 변경했다가 돌아갈 때 쓰는 백업). 평소에는 셋이 다 같지만, sudo나 setuid 바이너리가 실행되면 effective UID가 잠시 root(0) 로 바뀝니다. 이게 "sudo가 어떻게 권한을 빌리는가" 의 핵심 메커니즘입니다.

핵심

주저리 주저리 설명이 길었는데, 결국 핵심만 요약하면 다음 그림과 표만 기억하면 됩니다.

1[사용자 측] [자원 측]
2 UID owner UID
3 primary GID ──매칭──▶ owner GID
4 supplementary GIDs mode bits (rwxrwxrwx)
5
6사용자가 자원에 접근할 때마다 커널이 (UID, GIDs, mode bits) 매칭을 검사
Linux IAM
사용자 UID/GID Principal (User/Role의 ARN)
/etc/passwd, /etc/group IAM User/Group/Role 정의
파일의 owner UID + GID + mode Resource Policy (예: S3 bucket policy)
커널의 권한 체크 로직 IAM의 Policy 검사 엔진

2. sudo와 권한 위임 - 권한을 잠시 빌려쓴다

OS 권한 모델 패턴 중 중요한 패턴 중 하나가 권한 위임입니다. sudo 명령어를 사용할 때 대부분의 동작이 가능한 이유가 "잠시 빌려쓰는 권한" 이라는 사상때문입니다. 이 패턴은 IAM의 Role/STS를 이해하는데 필요하기 때문에 한 단계씩 풀어보겠습니다.

sudo가 만들어졌을까 - root의 위험성

위에서 잠깐 언급했지만 리눅스에는 root(UID: 0)라는 만능 사용자가 존재합니다. 모든 파일을 읽고 쓰고, 어떤 프로세스든 종료시키고, 시스템 파일도 마음대로 수정할 수 있습니다. 모든 일을 수행할 수 있기 때문에 사용하기는 편한데, 실수 한 번으로 시스템을 통째로 박살낼 수 있습니다.

1# 인터넷 곳곳에 전설로 남은 사고들
2rm -rf /usr/lib/ # 시스템 라이브러리 통째 삭제 → 부팅 불가
3chmod -R 777 / # 전체 파일을 누구나 쓸 수 있게 → 무료 개장
4dd if=/dev/zero of=/dev/sda # 디스크 전체를 0으로 → 복구 불가

무엇보다 보안적으로 위험합니다. root로 로그인한 셸에서 악성 스크립트를 실수로 실행하면 그 스크립트는 시스템 전체에 자유롭게 접근할 수 있게 됩니다. (만약 일반 사용자였다면 그 사용자의 홈 디렉토리에만 접근할 수 있었을 겁니다.)

그래서 Unix 세계에선 "평소엔 일반 사용자로, root는 정말 필요할 때만 잠시 빌려쓴다" 는 황금률이 생겼습니다.  이걸 기술적으로 가능하게 해주는 게 sudo입니다.

sudo의 라이프사이클 — 한 줄 명령 안에서 일어나는 6단계

wook 사용자가 sudo systemctl restart ngin 를 친 순간, 시스템 안에서는 다음 일이 차례로 일어납니다.

Loading diagram…

1. sudo 바이너리 실행 — /usr/bin/sudo 가 실행됩니다. 이 파일을 ls -l 해보면 -rwsr-xr-x 처럼 s 비트가 붙어 있어요. 이게 setuid 비트 (실행할 때 파일 소유자의 UID로 변신) 인데, sudo 바이너리는 root 소유라서 실행되는 순간 sudo 프로세스 자체가 root 권한이 됩니다.
2. 정책 확인 — sudo가 /etc/sudoers 를 읽어 "wook이 systemctl restart nginx를 실행할 자격이 있는가?" 를 검사합니다.
3. 인증 — 정책에 따라 wook의 비밀번호 또는 MFA를 요구합니다. (한 번 입력하면 5분간 캐시되어 재입력 안 받음)
4. 감사 로그 — 누가 언제 무엇을 시도했는지 /var/log/auth.log 에 남깁니다. 권한이 거부되어도 기록됩니다.
5. 자식 프로세스 fork — fork() + setuid(0) 으로 root 권한의 자식 프로세스를 만들어 systemctl restart nginx 를 거기서 실행합니다.
6. 권한 회수 — 자식 프로세스가 끝나면 wook의 셸로 돌아옵니다. wook의 셸은 처음부터 끝까지 일반 사용자 권한이었어요. root 권한은 그 명령이 실행되는 동안만 존재하게 됩니다.

핵심은 2, 3, 4번 입니다. 이 (인증 + 인가 + 감사) 의 3종 세트가 IAM에서도 그대로 등장합니다.

2 (인가)"이 사람이 이 명령을 실행할 자격이 있나?" 를 정책 파일이 정합니다. 정책은 사용자별·명령별로 다 다를 수 있어요.
3 (인증): 단지 "wook이 sudo를 칠 줄 안다" 가 아니라 "실제로 wook 본인이다" 를 매번 확인합니다.
4 (감사): 모든 권한 상승이 로그에 박힙니다. 사고가 나도 추적 가능.

/etc/sudoers 들여다보기

sudoers 파일은 "누가 어디서 누구로 무엇을 할 수 있는가" 를 표현하는 작은 DSL(Domain-Specific Language)입니다. 형식은 단순합니다.

1USER HOST=(RUN_AS_USER) [NOPASSWD:] COMMAND
2
3# 실제 예시
4# 1. wheel 그룹 멤버는 모든 명령을 root로 실행 가능 (비밀번호 필요)
5%wheel ALL=(ALL) ALL
6
7# 2. wook은 비밀번호 없이 nginx 재시작만 가능
8wook ALL=(root) NOPASSWD: /usr/bin/systemctl restart nginx
9
10# 3. ops 그룹은 deploy 사용자로 배포 스크립트 실행 가능 (비밀번호 필요)
11%ops ALL=(deploy) /opt/scripts/deploy.sh
12
13# 4. ci 사용자는 어떤 호스트에서든 비번 없이 어떤 명령도 가능 (위험! 쓰면 안 되는 DSL)
14ci ALL=(ALL) NOPASSWD: ALL
위치 의미 2번 줄에서는
USER 누가 sudo를 호출할 수 있는가 wook
HOST 어느 머신에서 (다중 호스트 sudoers 공유 시) ALL (어디서든)
(RUN_AS_USER) 어떤 사용자로 변신할 수 있는가 (root) — root로만
NOPASSWD 비밀번호 면제 여부 NOPASSWD: 있음 → 면제
COMMAND 어떤 명령을 실행할 수 있는가 /usr/bin/systemctl restart nginx

여기서 이 표현 모델을 IAM Policy 와 비교해보면 다음과 같습니다.

sudoers 요소 IAM Policy 대응 의미
USER (wook, %wheel) Principal 누가
RUN_AS_USER ((root), (deploy)) AssumeRole의 target Role 누구로 변신
COMMAND (/usr/bin/systemctl ...) Action + Resource 무엇을
NOPASSWD 여부 Condition: { Bool: { aws:MultiFactorAuthPresent } } 어떤 조건에서
/var/log/auth.log CloudTrail 감사
INFO

💡 노트 - sudosetuid 바이너리 하나로 구현된 이유

권한 상승 코드를 한 군데로 집중시켜야 감사·테스트·잠금이 가능하기 때문입니다. 만약 모든 프로그램이 자기만의 권한 상승 로직을 가졌다면 보안 취약점이 분산되어 통제 불가능했을 겁니다. AWS도 같은 사상으로 STS라는 단일 진입점을 통해서만 권한 상승을 허용합니다.

sudo는 단순한 "권한 상승" 도구가 아니라 (인증 + 인가 + 변신 + 감사) 가 결합된 작은 보안 시스템이라는 것이 핵심입니다. 클라우드의 IAM의 STS·Role·Policy·CloudTrail은 정확히 이 4종 세트를 분산 환경으로 추상화한 것 뿐입니다.

3. POSIX ACL이 푸는 방식

POSIX ACL(Access Control List: 접근 제어 목록) 은 9bts 권한에 개별 사용자/그룹별 규칙을 추가한 것입니다. 위의 예시를 POSIX ACL로 다시 풀어보겠습니다.

1# 1. 기본 9비트는 owner=wook, group=dev, mode=770 (others 차단)
2chown wook:dev /shared/project
3chmod 770 /shared/project
4
5# 2. ACL로 사용자별 예외 추가
6# -m: modify, u: user, 형식 = u:사용자명:권한
7setfacl -m u:철수:r-x,u:영희:rwx /shared/project
8
9# 3. 확인
10getfacl /shared/project
11
12# file: shared/project
13# owner: wook
14# group: dev
15user::rwx ← 9비트의 owner 권한 (wook)
16user:charlie:r-x ← ACL: 철수에게만 read+execute
17user:dave:rwx ← ACL: 영희에게만 read+write+execute
18group::rwx ← 9비트의 group 권한 (dev그룹)
19mask::rwx ← (잠시 뒤에 설명)
20other::--- ← 9비트의 others 권한

핵심은 9비트의 owner/group/others 외에 "named user" 와 "named group" 항목을 추가할 수 있다는 점입니다.

getfacl 출력 의미

처음 getfacl출력을 보면 어지럽게 느껴질 수 있습니다. 하나씩 살펴보면 크게 어렵지 않습니다.

라인 형식 의미
# file:, # owner:, # group: 헤더 — 파일 메타데이터
user::rwx owner의 권한 (9비트의 첫 3비트)
user:charlie:r-x named user charlie의 추가 권한 (ACL 본체)
group::rwx owning group의 권한 (9비트의 가운데 3비트)
group:designers:r-- named group designers의 추가 권한
mask::rwx ACL 항목들의 권한 상한선
other::--- others의 권한 (9비트의 마지막 3비트)
default:user::rwx (디렉토리에만) 새로 만들어지는 자식의 기본 권한

:: (콜론 두 개) 가 붙은 항목은 9비트와 1:1 대응되고, :사용자명: 가 들어간 항목이 POSIX ACL이 새로 추가하는 부분입니다.

Related Articles