Skip to content
게으른 엔지니어의 기술 블로그
Go back

리눅스 ACL이 뭐길래 — POSIX ACL과 mask 이해하기

어제 Docker에서 Hermes 돌리다가 uid 하나 때문에 하루를 갈아넣은 이야기에서 ACL을 걸었다가 실패한 이야기를 짧게 썼는데, 그때는 “안 됐다”까지만 넘어갔다. 이번엔 ACL이 정확히 뭔지, 왜 그걸로 접근했는지, 왜 안 됐는지를 제대로 짚어보려고 한다.

문제 상황 복기

컨테이너 안 프로세스는 uid 10000, 나는 uid 1000이었다. 둘이 파일을 주고받아야 하는데 권한이 안 맞았다. 그룹을 새로 만들어서 묶어봤는데, 이것도 결국 안 됐다(이유는 뒤에서 설명한다). 그다음에 시도한 게 ACL이었다.

왜 그냥 rwx로는 안 되나

리눅스의 기본 권한 모델은 파일 하나당 딱 세 부류로만 나뉜다.

-rw-r--r-- 1 decipher decipher  파일
소유자(owner)  그룹(group)  나머지(other)

소유자 하나, 그룹 하나, 그 외 전부(other) — 이 세 슬롯 안에서만 권한을 나눠줄 수 있다. “이 특정 사용자 한 명한테만 추가로 쓰기 권한을 주고 싶다”거나 “이 그룹 A는 읽기만, 그룹 B는 쓰기까지” 같은 세밀한 조정이 안 된다. 그룹을 새로 만들어서 두 uid를 묶어보려던 것도 결국 이 세 슬롯 모델 안에서 어떻게든 끼워 맞추려는 시도였다.

ACL이란

**ACL(Access Control List)**은 이 세 슬롯 모델을 확장해서, 파일 하나에 임의의 사용자나 그룹을 원하는 만큼 추가하고 각각 다른 권한을 줄 수 있게 해주는 메커니즘이다. “소유자/그룹/나머지” 세 칸이 아니라, 필요한 만큼 규칙을 리스트로 쌓아올리는 방식이라고 생각하면 된다.

POSIX ACL

리눅스에서 이 ACL을 실제로 구현한 표준이 POSIX ACL이다(ext4, xfs 등 대부분의 파일시스템이 지원). 명령어는 두 개만 알면 된다.

getfacl <>   # 지금 걸려있는 ACL 확인
setfacl <> <>   # ACL 설정
setfacl -m g:hermes-share:rwx ~/hermes-workspace

이러면 hermes-share 그룹에게 그 디렉토리에 rwx 권한을 추가로 부여한다. 소유자/그룹 슬롯을 안 건드리고도 특정 그룹 하나를 콕 집어서 권한을 얹을 수 있다.

거기다 -d(default) 옵션을 쓰면, 지금 있는 파일뿐 아니라 앞으로 그 디렉토리 안에 새로 생기는 파일에도 자동으로 같은 ACL이 상속되게 할 수 있다.

setfacl -d -m g:hermes-share:rwx ~/hermes-workspace

여기까지만 보면 완벽한 해결책 같다. 실제로 나도 그렇게 생각했다.

함정: mask

getfacl로 확인해보면 낯선 줄이 하나 껴 있는데, 이게 핵심이다. 실제로 명령어를 순서대로 따라가면서 뭐가 바뀌는지 눈으로 보자.

1단계 — ACL을 건다

$ setfacl -m g:hermes-share:rwx ~/hermes-workspace
$ getfacl ~/hermes-workspace
# owner: decipher
# group: decipher
user::rwx
group::rwx
group:hermes-share:rwx
mask::rwx
other::r-x

hermes-share 그룹에 rwx를 얹었다. 이 시점의 mask::rwx를 기억해두자.

2단계 — 실제 유효 권한 계산법

group:으로 시작하는 모든 ACL 항목의 실제 적용 권한은, “그 항목에 써있는 권한” 그대로가 아니라 “그 항목과 mask를 AND(교집합)한 값”이다.

유효 권한(effective) = ACL 항목의 권한  AND  mask

지금은 rwx AND rwx = rwx라서 문제없이 다 적용된다. 그런데 mask가 낮으면 어떻게 될까? mask가 r--인 경우를 가정해보면:

rwx (ACL 항목)  AND  r--  (mask)  =  r--   ← 실제로는 읽기만 적용됨

ACL 항목에 rwx라고 적혀 있어도, mask에 없는 w/x는 그냥 무시된다. mask는 ACL 항목들의 상한선인 셈이다.

3단계 — 그 위에서 다른 프로그램이 파일을 만들고 chmod를 부른다

chmod 숫자 표기(600 같은 거)가 헷갈린다면 펼쳐보기 — 아는 사람은 건너뛰어도 됨

chmod의 숫자(octal) 표기는 세 자리 숫자로, 각 자리가 순서대로 소유자(owner) / 그룹(group) / 나머지(other)를 뜻한다. 각 자리 숫자는 읽기(r)=4, 쓰기(w)=2, 실행(x)=1을 더한 값이다.

권한
r-- (읽기만)4
-w- (쓰기만)2
--x (실행만)1
rw- (읽기+쓰기)4+2 = 6
r-x (읽기+실행)4+1 = 5
rwx (전부)4+2+1 = 7
--- (권한 없음)0

600을 자리별로 풀면:

6    0    0
│    │    └─ other: 0 = ---  (남 전부 권한 없음)
│    └────── group: 0 = ---  (그룹 권한 없음)
└─────────── owner: 6 = rw-  (소유자만 읽기+쓰기)

chmod 600은 "소유자만 읽고 쓸 수 있고, 그룹이든 남이든 전혀 접근 못 하게" 만드는 명령어다. 아래에서 볼 ls -la-rw-------가 바로 이 600을 문자로 표현한 것과 같은 뜻이다 (rw- + --- + ---).

$ echo hello > ~/hermes-workspace/파일
$ ls -la ~/hermes-workspace/파일
-rw-r--r-- 1 decipher hermes-share 6 Jul  8 00:00 파일

$ chmod 600 ~/hermes-workspace/파일
$ ls -la ~/hermes-workspace/파일
-rw-------+ 1 decipher hermes-share 6 Jul  8 00:00 파일

ls -la만 보면 파일 소유자(decipher)와 그룹(hermes-share)은 전혀 안 바뀌었다. 바뀐 건 권한 비트뿐이고, 맨 끝에 + 표시가 추가된 게 눈에 띈다(이 파일에 ACL이 얹혀 있다는 표시). 진짜 무슨 일이 일어났는지는 getfacl로 봐야 보인다.

$ getfacl ~/hermes-workspace/파일
user::rw-
group::rwx	#effective:---
group:hermes-share:rwx	#effective:---
mask::---

chmod 600이 “그룹 권한 없음”을 지시하니까, ACL의 그룹 비트가 아니라 mask 자체를 rwx에서 ---로 덮어써버린 것이다. chmod가 만들어진 시절엔 ACL이라는 개념 자체가 없었고, ACL이 나중에 그 위에 얹히면서 “chmod로 그룹 권한을 바꾸면 mask가 따라간다”는 규칙으로 하위 호환을 맞춘 결과다.

4단계 — 그래서 결과가 이렇게 된다

[chmod 전]  group:hermes-share:rwx  AND  mask::rwx  =  rwx   (정상 작동)
[chmod 후]  group:hermes-share:rwx  AND  mask::---  =  ---   (완전히 막힘)

group:hermes-share:rwx 항목 자체는 3단계 출력에서 보다시피 지워지지 않고 그대로 남아 있다. 다만 mask가 0으로 깎이면서 AND 연산 결과가 전부 0이 되어버렸다. ACL 설정을 아무리 잘 해놔도, 그 뒤에 누군가(또는 어떤 프로그램)가 chmod를 한 번 부르는 순간 mask가 갈아치워지면서 전부 무력화되는 셈이다.

정리

이 mask 동작 방식을 미리 알았다면, 어제 글에서처럼 이것저것 시도하다가 한 걸음 늦게 알아차리는 일은 없었을 거다. 정확히 이해하고 있으면 막을 수 있는 삽질이라, 여기에 기록을 남긴다.


Share this post:

Previous Post
Docker에서 Hermes 돌리다가 uid 하나 때문에 하루를 갈아넣은 이야기
Next Post
docker run --user는 안 되고 HERMES_UID는 됐던 이유