LODY/정리

Ubuntu 서버 운영 / 사용자와 권한 모델

사용자와 권한 모델

🟣 root 하나로 다 하면 안 되는 이유

서버를 처음 받으면 손에 쥐는 건 root 계정 하나다. 편하다. 아무 파일이나 열고, 어떤 서비스든 띄우고, 무엇이든 지운다. 그런데 이 편함이 그대로 위험이다. 배포 스크립트에 오타 하나가 있어서 엉뚱한 경로를 지우려 해도 root라면 커널이 막지 않는다. 웹 서비스가 뚫렸을 때 그 프로세스가 root로 돌고 있었다면, 공격자는 서버 전체를 얻는다.

그래서 실제 운영에서는 일을 나눈다. 사람마다 계정을 따로 두고, 서비스마다 최소 권한만 가진 전용 계정으로 띄운다. 이 분리가 성립하려면 "누가"와 "무엇을 할 수 있는가"를 시스템이 어떻게 표현하는지부터 알아야 한다. 이 장은 새 계정을 하나 만드는 일에서 출발해, 그 밑에 깔린 리눅스 권한 모델까지 내려간다.


🟣 사용자는 결국 숫자다

리눅스에서 사용자 이름은 사람이 읽기 위한 껍데기고, 커널이 실제로 다루는 것은 숫자다. 사용자마다 UID(user id), 그룹마다 GID(group id)라는 정수가 붙는다. 파일의 소유자도, 프로세스의 주인도 전부 이 숫자로 기록된다. lody라는 이름은 그 숫자에 붙인 별명일 뿐이다.

이 대응 관계가 세 파일에 나뉘어 저장된다. /etc/passwd에는 사용자 이름, UID, GID, 홈 디렉터리, 로그인 셸이 들어간다. 비밀번호의 해시는 아무나 못 읽도록 /etc/shadow에 따로 뺐다. 그룹 정보는 /etc/group에 있다.

$ id lody
uid=1000(lody) gid=1000(lody) groups=1000(lody),27(sudo)

id 명령이 이 숫자들을 한눈에 보여준다. 여기서 눈여겨볼 것은 groupssudo가 들어 있다는 점이다. 이 계정이 관리자 권한을 위임받을 수 있는 이유가 바로 이 그룹 소속에 있는데, 이건 뒤에서 다시 본다.


🟣 계정을 만든다: useradd와 adduser

새 팀원에게 계정을 주거나, 배포 전용 계정을 파려면 사용자를 추가한다. 명령이 두 가지라 처음엔 헷갈렸다. useraddadduser다.

useradd는 커널에 가까운 저수준 명령이다. 옵션을 일일이 주지 않으면 홈 디렉터리도 안 만들고 셸도 기본값으로 둔다. adduser는 우분투가 그 위에 씌운 대화형 스크립트로, 홈 디렉터리 생성, 비밀번호 설정, 기본 파일 복사까지 알아서 챙긴다. 사람 계정을 만들 때는 adduser가 편하고, 스크립트로 자동화할 때는 옵션이 명시적인 useradd가 낫다.

$ sudo useradd -m -s /bin/bash -c "Deploy account" deploy

-m은 홈 디렉터리를 만들고, -s는 로그인 셸을 지정한다. 앞 장에서 본 그 로그인 셸 필드가 여기서 정해진다. 이렇게 만든 계정은 아직 아무 그룹에도 특별히 속하지 않은, 권한이 가장 적은 상태다.


🟣 그룹에 넣을 때 -a를 빼면 벌어지는 일

계정에 권한을 주는 가장 흔한 방법은 적절한 그룹에 넣는 것이다. 관리자 권한이 필요하면 sudo 그룹에, 도커를 쓰게 하려면 docker 그룹에 넣는다. 이때 쓰는 명령이 usermod인데, 여기에 유명한 함정이 하나 있다.

$ sudo usermod -aG sudo deploy   # 맞는 방법
$ sudo usermod -G sudo deploy    # 위험: 기존 그룹이 다 날아간다

-G는 사용자의 보조 그룹 목록을 통째로 덮어쓴다. -a는 append, 즉 "기존 목록에 추가"라는 뜻이다. -a 없이 -G sudo만 주면 그 사용자가 원래 속해 있던 다른 그룹들이 전부 지워지고 sudo 하나만 남는다. 도커 권한이나 특정 서비스 접근 권한이 소리 없이 사라지는 사고가 여기서 난다. 그룹을 추가할 때는 반드시 -aG를 함께 쓴다는 걸 몸으로 익혔다.


🟣 권한은 아홉 개의 비트다

계정을 만들었으니 이제 그 계정이 파일을 읽고 쓰고 실행할 수 있는지가 정해져야 한다. 리눅스의 고전적인 파일 권한은 놀랄 만큼 단순하다. 대상을 셋으로 나누고(소유자, 그룹, 그 외), 각각에 읽기·쓰기·실행 세 가지 권한을 준다. 세 대상 곱하기 세 권한, 아홉 개의 비트가 파일 하나의 접근 권한을 전부 표현한다.

$ ls -l deploy.sh
-rwxr-x--- 1 deploy deploy 214 Jul 13 10:22 deploy.sh

맨 앞 문자를 뺀 rwxr-x---가 그 아홉 비트다. 앞 세 자리 rwx는 소유자 deploy가 읽고 쓰고 실행할 수 있다는 뜻이고, 가운데 r-x는 같은 그룹이 읽고 실행만, 마지막 ---는 그 외 사용자는 아무것도 못 한다는 뜻이다. 이 세 묶음을 각각 8진수 한 자리로 읽으면 chmod 750 같은 숫자 표기가 된다. r=4, w=2, x=1을 더한 값이다.

소유자rwx = 7그룹r-x = 5그 외--- = 0

권한을 이렇게 세 계층으로 쪼개 둔 덕에 "소유자만 고칠 수 있고, 팀은 읽기만, 나머지는 접근 불가" 같은 정책을 숫자 세 자리로 표현한다. 서비스 계정에 필요한 최소 권한만 주는 원칙이 이 위에서 돌아간다.


🟣 passwd는 어떻게 shadow를 고치나: setuid

여기서 한 가지 모순이 눈에 걸렸다. 일반 사용자가 자기 비밀번호를 바꾸는 passwd 명령을 생각해 보자. 비밀번호 해시는 /etc/shadow에 있고, 이 파일은 root만 쓸 수 있다. 그런데 일반 사용자가 passwd로 자기 비밀번호를 바꿀 수 있다. 권한이 없는 사용자가 어떻게 root만 쓸 수 있는 파일을 고치는가.

답은 setuid라는 특수 비트에 있다. 아까 아홉 개의 비트를 말했지만, 그 위에 세 개의 특수 비트가 더 있다. 그중 setuid 비트가 실행 파일에 걸려 있으면, 그 프로그램은 실행한 사람이 아니라 파일 소유자의 권한으로 돈다.

$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 59976 ... /usr/bin/passwd

소유자 자리의 x가 있어야 할 곳에 s가 보인다. 이게 setuid 표시다. passwd의 소유자는 root이므로, 일반 사용자가 passwd를 실행하면 그 프로세스는 잠깐 root 권한을 얻어 /etc/shadow를 고치고 끝난다.

이 대목이 앞서 정리한 리눅스 시스템 프로그래밍과 직접 이어진다. 프로세스는 자기가 어떤 사용자인지를 나타내는 UID를 하나만 갖는 게 아니라, 실제 UID와 실효 UID(effective UID)를 나눠 갖는다. 권한 판단은 실효 UID로 한다. 평소에는 둘이 같지만, setuid 프로그램을 실행하면 실효 UID만 파일 소유자의 것으로 바뀐다. 프로세스가 fork로 태어날 때 부모의 크레덴셜을 물려받는다는 그 이야기가, 여기서는 "실행 파일의 비트가 자식의 실효 권한을 끌어올린다"는 형태로 다시 나타나는 셈이다.

물론 이 힘은 위험하다. setuid 프로그램에 버그가 있으면 그 버그가 곧 root 권한 탈취 통로가 된다. 그래서 내가 만든 서비스 계정 파일에 함부로 setuid를 걸지 않는 것이 원칙이고, 시스템의 setuid 파일 목록을 주기적으로 점검하는 것이 보안 점검의 기본이 된다.


🟣 sudo: 권한을 통째로 주지 않고 빌려준다

관리자 작업을 하려고 매번 root로 로그인하는 것은 앞서 말한 위험을 그대로 안는 일이다. 대신 쓰는 것이 sudo다. sudo는 특정 사용자가 특정 명령만 root 권한으로 실행하도록 위임한다. 평소에는 자기 권한으로 일하다가, 필요한 순간에만 딱 한 명령에 대해 권한을 빌린다.

누가 sudo를 쓸 수 있는지는 /etc/sudoers 파일이 정한다. 우분투에서는 대개 sudo 그룹에 속한 사용자에게 권한을 준다. 앞에서 idsudo가 찍혀 있던 계정이 관리자 작업을 할 수 있던 이유가 이것이다. 이 파일은 직접 편집하지 않고 visudo로 연다. visudo는 저장 전에 문법을 검사해서, 오타로 파일을 망가뜨려 아무도 sudo를 못 쓰게 되는 사고를 막아 준다.

여기서 자주 혼동하는 su와의 차이를 짚어 둔다. su는 아예 다른 사용자로 셸을 갈아타는 것이고, sudo는 내 셸을 유지한 채 명령 하나만 상위 권한으로 실행하는 것이다. 서버에서는 root 셸을 통째로 여는 su보다, 무엇을 누가 실행했는지 로그로 남고 범위가 좁은 sudo를 기본으로 삼는다. 권한은 통째로 넘기지 않고 필요한 만큼만 빌린다는 원칙이 여기서도 반복된다.


🟣 정리

  • 리눅스는 사용자와 그룹을 이름이 아니라 UID·GID라는 숫자로 다룬다. /etc/passwd, /etc/shadow, /etc/group이 그 대응을 나눠 저장한다.
  • 계정 생성은 저수준 useradd와 우분투 편의 도구 adduser로 한다. 그룹에 넣을 때 usermod-a를 빠뜨리면 기존 그룹이 통째로 지워진다.
  • 파일 권한은 소유자·그룹·그 외 세 대상에 읽기·쓰기·실행을 준 아홉 개의 비트다. chmod 750 같은 숫자는 이 비트를 8진수로 읽은 것이다.
  • setuid 비트가 걸린 실행 파일은 실행자가 아니라 소유자 권한으로 돈다. passwd가 일반 사용자의 손으로 /etc/shadow를 고칠 수 있는 원리이고, 프로세스의 실효 UID 개념과 이어진다.
  • root로 상시 작업하지 않고 sudo로 필요한 명령만 권한을 빌린다. 누가 쓸 수 있는지는 sudo 그룹과 /etc/sudoers가 정하고, 편집은 visudo로 한다.

이 장에서 실무로 남는 감각은 "최소 권한"이다. 면접에서 리눅스 권한을 물으면 chmod 777로 얼버무리기 쉬운데, setuid와 실효 UID까지 내려가서 passwd의 동작을 설명할 수 있으면 권한을 진짜로 이해했다는 신호가 된다. 계정과 권한이 정해졌으니, 이제 그 권한으로 실제 서비스를 띄울 차례다. 그런데 서비스를 띄우면 곧바로 "그 포트를 이미 누가 쓰고 있다"는 문제와, "그게 계속 떠 있게 하려면 어떻게 하나"라는 문제를 만난다. 다음 장은 프로세스와 포트, 그리고 systemd다.