신입 엔지니어 재현 씨가 온보딩 첫 주에 Tomcat을 root 계정으로 실행했습니다. 서비스는 잘 돌아갔고 아무도 몰랐습니다. 두 달 뒤, 보안 감사팀이 서버 접근 로그를 분석하다가 발견합니다. "이 WAS, root로 실행 중이네요. 즉시 수정 요청합니다." 긴급 조치 티켓이 열리고 서비스 재배포와 권한 재설정 작업이 주말을 날렸습니다.
root로 실행된 서비스가 공격당하면 서버 전체가 뚫립니다. 계정과 권한 설계는 선택이 아닙니다.
- 1useradd/usermod/userdel로 사용자를 생성·수정·삭제할 수 있다
- 2chmod 숫자 표기로 파일 권한을 읽고 설정할 수 있다
- 3visudo로 특정 명령어만 sudo를 허용하는 제한 설정을 할 수 있다
- 4서비스 전용 계정을 생성하고 디렉터리 소유권을 올바르게 설정할 수 있다
- 5/etc/passwd와 /etc/shadow의 구조를 읽고 해석할 수 있다
id && groupswhich useradd groupadd usermod userdelwhich sudo && sudo -V | head -1사용자와 그룹 관리
사용자 계정의 구조 — /etc/passwd와 /etc/shadow
보안 감사에서 "서비스 계정으로 SSH 로그인이 가능한 상태입니다"라는 지적을 받았습니다. /etc/passwd를 보니 Tomcat 계정의 쉘이 /bin/bash로 설정되어 있었고, 비밀번호도 설정된 상태였습니다. 공격자가 이 계정을 탈취하면 서버에 접속해 쉘을 열 수 있습니다. 계정이 어떻게 저장되는지 이해해야 서비스 계정을 올바르게 설정하고 감사 지적을 받지 않을 수 있습니다.
Linux는 모든 사용자를 /etc/passwd에 기록하고, 실제 패스워드 해시는 루트만 읽을 수 있는 /etc/shadow에 분리 저장합니다. 이 분리 덕분에 일반 사용자가 암호화된 패스워드를 볼 수 없어 무차별 대입 공격을 막습니다.
확대
cat /etc/passwd | tail -3
# 형식: 사용자명:x:UID:GID:주석(GECOS):홈디렉터리:로그인쉘
nginx:x:997:995:Nginx Web Server:/var/cache/nginx:/sbin/nologin
appuser:x:1001:1001::/opt/app:/sbin/nologin
sudo cat /etc/shadow | grep appuser
# '!' = 패스워드 로그인 비활성화 (서비스 계정의 정상 상태)
appuser:!:19800:0:99999:7:::
UID 범위 의미:
| 범위 | 의미 |
|---|---|
0 | root — 모든 권한 |
1 ~ 999 | 시스템 계정 (서비스용) |
1000+ | 일반 사용자 (로그인 계정) |
# useradd — 일반 vs 서비스 계정
sudo useradd -m -s /bin/bash devuser # 일반 사용자
sudo useradd -r -s /sbin/nologin -d /opt/app -m appuser # 서비스 계정
# -r: 시스템 계정(UID < 1000), -s: 쉘 지정, -d: 홈 경로, -m: 홈 생성
# usermod — 계정 수정
sudo usermod -aG appgroup appuser # 그룹 추가 (-a 없으면 기존 그룹 제거됨)
sudo usermod -L devuser # 계정 잠금
sudo usermod -U devuser # 잠금 해제
# groupadd / gpasswd
sudo groupadd appgroup
sudo gpasswd -a appuser appgroup # 그룹에 사용자 추가
sudo gpasswd -d appuser appgroup # 그룹에서 제거
# 확인
id appuser
# uid=1001(appuser) gid=1001(appuser) groups=1001(appuser),1002(appgroup)
파일 권한 체계
rwx 권한 구조와 chmod
파일 권한은 서비스의 보안과 안정성을 동시에 결정합니다. 설정 파일에 실행 권한이 없으면 배포 자동화가 멈추고, DB 비밀번호가 든 파일에 644를 걸어두면 서버에 접근한 누구나 그 내용을 읽을 수 있습니다. 숫자 세 자리의 의미를 체계적으로 이해해야 의도한 대로 접근을 제어하고, 보안 감사에서 지적받지 않을 수 있습니다.
확대
배포한 설정 파일에 DB 비밀번호가 들어있었는데 권한이 644로 열려있어서, 서버에 접근 가능한 모든 계정이 그 파일을 읽을 수 있었습니다. 보안 감사 항목 중 가장 흔하게 적발되는 것이 이 권한 설정 오류입니다. 반대로 실행 스크립트에 실행 권한을 빠뜨려 배포 자동화가 멈추는 경우도 마찬가지입니다. 숫자 세 자리의 의미를 정확히 이해해야 의도대로 접근을 제어할 수 있습니다.
모든 파일·디렉터리에는 소유자(owner) / 그룹(group) / 기타(others) 세 계층으로 읽기(r=4) · 쓰기(w=2) · 실행(x=1) 권한이 독립적으로 설정됩니다.
ls -la /opt/app/
# 형식: 권한(10자) 링크수 소유자 그룹 크기 날짜 이름
drwxr-x--- 3 appuser appgroup 4096 Jan 15 09:00 .
-rwxr-xr-x 1 appuser appgroup 8192 Jan 15 09:00 start.sh # 755
-rw-r--r-- 1 appuser appgroup 512 Jan 15 09:00 app.conf # 644
-rw------- 1 appuser appgroup 128 Jan 15 09:00 secret.key # 600
자주 쓰는 권한 조합:
| 숫자 | 권한 | 용도 |
|---|---|---|
755 | rwxr-xr-x | 실행 스크립트, 디렉터리 |
644 | rw-r--r-- | 설정 파일, HTML |
600 | rw------- | 개인 키, 비밀 파일 |
750 | rwxr-x--- | 서비스 디렉터리 (others 차단) |
# 숫자 표기 (권장)
chmod 755 start.sh
chmod 644 app.conf
chmod 600 secret.key
chmod 750 /opt/app/
# 문자 표기 (부분 변경 시 유용)
chmod +x deploy.sh # 실행 권한 추가
chmod o-rwx secret.key # others 권한 전체 제거
# 소유자·그룹 변경
sudo chown appuser:appgroup /opt/app/log.txt
sudo chown -R appuser:appgroup /opt/app/ # 재귀
특수 권한 (보안 감사 필수 확인 항목):
SUID(s)가 root 소유 실행 파일에 설정되면 일반 사용자도 root 권한으로 실행됩니다. /tmp의 Sticky Bit(t)는 자신이 만든 파일만 삭제 가능하게 합니다.
# 서버 내 SUID 파일 전체 목록 (의심 파일 확인)
sudo find / -perm -4000 -type f 2>/dev/null
# passwd, sudo, ping 등 정상 파일 외 항목은 즉시 조사
1. 실습 파일 생성
mkdir -p /tmp/perm-test && touch /tmp/perm-test/{app.jar,config.yml,deploy.sh,secret.key} && ls /tmp/perm-test/2. 용도별 권한 적용
chmod 644 /tmp/perm-test/app.jar && chmod 640 /tmp/perm-test/config.yml && chmod 700 /tmp/perm-test/deploy.sh && chmod 600 /tmp/perm-test/secret.key && ls -la /tmp/perm-test/3. 권한 숫자 읽기 연습
stat -c '%n: %a (%A)' /tmp/perm-test/*- ls -la 출력에서 먼저 각 파일의 권한 문자열 첫 자리(-rwx------)를 보고, 그 다음 소유자:그룹 필드 확인 — 파일 유형에 맞는 권한인지 순서대로 체크
- 권한 기준: 600 미만이면 소유자도 접근 불가(잘못된 설정), 644 이상 others 쓰기 허용이면 보안 위험 — secret.key/privkey.pem은 반드시 600, config.yml은 640이 최대
- deploy.sh가 700인데 서비스 계정이 실행 실패하면 → 소유자가 서비스 계정과 다른 것 — stat deploy.sh로 소유자 확인 후 chown <서비스계정> deploy.sh 실행
sudo 설정
/etc/sudoers — 최소 권한으로 sudo 제한
배포 계정에 NOPASSWD: ALL을 설정해뒀다가 보안 감사에서 "root 권한과 동일한 계정"으로 지적을 받았습니다. 해커가 이 계정 하나를 탈취하면 서버 전체를 장악할 수 있습니다. 반대로 권한을 너무 좁게 설정하면 배포 스크립트가 중간에 실패합니다. 어떤 명령어에만 sudo를 허용할지 정밀하게 지정하는 것이 보안과 운영 자동화를 동시에 잡는 방법입니다.
sudo는 일반 계정이 특정 명령어를 root 권한으로 실행하게 합니다. ALL=(ALL) ALL은 사실상 root와 동일합니다. 실무에서는 필요한 명령어만 허용하는 최소 권한(least privilege) 원칙을 적용합니다.
/etc/sudoers는 반드시 visudo로만 편집합니다. visudo는 저장 전 문법을 검사해 오류로 인한 sudo 잠금을 방지합니다.
# 전용 파일로 관리 (권장 — 메인 파일 직접 수정하지 않음)
sudo visudo -f /etc/sudoers.d/appuser
# /etc/sudoers.d/appuser 내용 예시
# 형식: 사용자 호스트=(실행계정) [NOPASSWD:] 명령어
# 전체 허용 (위험 — 피할 것)
appuser ALL=(ALL) ALL
# 특정 명령어만, 패스워드 없이 (서비스 재시작용 — 실무 표준)
appuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart tomcat
# 여러 명령어
appuser ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart tomcat, /usr/bin/systemctl status tomcat
# 그룹 단위 설정
%appgroup ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart tomcat
# sudo 로그 확인 (누가 언제 어떤 명령어를 실행했는지)
sudo journalctl _COMM=sudo --since "today" | grep appuser
Jan 15 14:23:01 server01 sudo: appuser : TTY=pts/0 ; PWD=/home/appuser ; USER=root ; COMMAND=/usr/bin/systemctl restart tomcat
확대
서비스 계정 관리 — 실무 핵심
왜 서비스를 root로 실행하면 안 되는가
root로 실행된 서비스는 프로세스가 탈취됐을 때 시스템 전체를 잃습니다. Tomcat에 원격 코드 실행 취약점이 있다면, 공격자는 Tomcat 프로세스 권한으로 명령어를 실행합니다. Tomcat이 root라면 백도어 계정 생성, 모든 파일 열람이 즉시 가능합니다. 서비스 전용 계정은 서비스가 동작하는 데 필요한 최소한의 권한만 가집니다.
# 위험한 상태 vs 안전한 상태
ps aux | grep -E 'tomcat|java' | grep -v grep
# 위험: root가 WAS를 실행 중
root 1234 2.5 8.3 3145728 340000 ? Sl 09:00 java -jar /opt/app/app.jar
# 안전: 서비스 계정으로 격리됨
appuser 1234 2.5 8.3 3145728 340000 ? Sl 09:00 java -jar /opt/app/app.jar
서비스 계정 설계 원칙:
| 원칙 | 내용 |
|---|---|
| 로그인 쉘 차단 | /sbin/nologin — 쉘 접속 불가 |
| 전용 홈 디렉터리 | 서비스 루트 디렉터리를 홈으로 설정 |
| 패스워드 비활성화 | /etc/shadow의 ! |
| 최소 파일 소유 | 서비스 디렉터리만 소유 |
1. WAS용 서비스 계정 및 그룹 생성
sudo useradd -r -s /sbin/nologin -d /opt/app -m appuser && sudo groupadd appgroup && sudo usermod -aG appgroup appuser && id appuser2. 디렉터리 소유권 및 권한 설정
sudo chown -R appuser:appgroup /opt/app && sudo chmod 750 /opt/app && ls -la /opt/ | grep app3. 서비스 계정 권한 검증
sudo -u appuser touch /opt/app/test.txt && ls -la /opt/app/test.txt4. sudo 제한 설정 (특정 명령어만 허용)
sudo visudo -f /etc/sudoers.d/appuser5. sudo 로그 확인
sudo journalctl _COMM=sudo --since '5 min ago' --no-pager | tail -10- id appuser 출력에서 먼저 그룹 목록에 appgroup이 있는지 보고, 그 다음 ls -la /opt/app 으로 소유자:그룹이 appuser:appgroup인지 확인 — 두 조건이 모두 맞아야 디렉터리 접근 가능
- /opt/app 권한 기준: 750이면 appgroup 소속 계정이 읽기/실행 가능(정상), 700이면 appuser 외 접근 불가(너무 좁음), 755이면 others도 접근 가능(너무 넓음)
- id appuser에 appgroup이 있는데 /opt/app 접근 실패하면 → 디렉터리 소유 그룹이 appgroup이 아닌 것 — ls -la /opt/ 로 app 디렉터리 소유 그룹 확인 후 chown -R appuser:appgroup /opt/app 실행
심화 — 공유 디렉터리에서 새 파일의 그룹은 누가 정하나
심화: setgid와 그룹 상속 — '공용 그룹'만으로는 부족하다
공유 작업 디렉터리를 만들 때 흔히 "공용 그룹을 만들고 디렉터리를 그 그룹 소유로 주면 끝"이라고 생각합니다. 실제로는 한 가지가 더 필요합니다 — 새로 생기는 파일의 그룹을 누가 정하는가 하는 문제입니다.
- 기본 동작: 생성자의 primary 그룹: 리눅스에서 새 파일의 그룹은 기본적으로 그 파일을 만든 사용자의 primary 그룹입니다. 그래서
alice가deploy소유 디렉터리에 파일을 만들어도 그 파일의 그룹은alice(개인 그룹)가 되고,bob은 그룹 권한으로 그 파일을 못 고칩니다. 공용 그룹을 만든 의미가 사라집니다. - setgid 비트로 그룹 상속: 디렉터리에 setgid 비트를 걸면(
chmod 2775 dir또는chmod g+s dir) 그 안에서 생성되는 파일과 하위 디렉터리는 디렉터리의 그룹을 상속합니다.ls -ld에서 그룹 실행 자리가s로 보이면 켜진 것입니다. 하위 디렉터리도 setgid를 물려받아 트리 전체에서 그룹이 유지됩니다. - 쓰기까지 되게 하려면 umask도: 그룹이 상속돼도 파일이
644(그룹 쓰기 없음)로 태어나면 여전히 못 고칩니다. 공유 작업 그룹에는 umask002(그룹 쓰기 허용)를 쓰거나, 더 정밀하게는 기본 ACL(setfacl -d -m g:deploy:rwx dir)로 강제합니다. - 그룹 변경은 즉시 반영이 아니다:
usermod -aG deploy alice로 그룹에 넣어도 alice의 현재 로그인 세션에는 반영되지 않습니다. 그룹 목록은 로그인 시점에 프로세스에 박히기 때문입니다. 재로그인하거나newgrp deploy로 새 그룹을 적용해야 합니다.
정리하면 공유 디렉터리는 '공용 그룹 소유 + setgid + 적절한 umask/ACL' 세 가지가 맞아야 완성이고, 멤버 추가 후에는 재로그인을 잊지 말아야 합니다.
상황: 여러 명이 함께 쓰는 /opt/deploy 디렉터리를 deploy 그룹 소유로 만들고 chmod 775까지 줬습니다. 그런데 A가 올린 파일을 B가 수정하려 하면 Permission denied, 반대도 마찬가지입니다. 그룹은 분명히 맞게 넣었는데 협업이 안 됩니다.
원인: 디렉터리에 setgid가 없어, 그 안에서 A가 만든 파일의 그룹이 deploy가 아니라 A의 개인 primary 그룹으로 생성됐습니다. 게다가 A의 umask가 022라 파일이 644(그룹 쓰기 없음)로 태어나, 설령 그룹이 맞았어도 B가 못 씁니다. 두 요인이 겹친 전형적 사례입니다.
진단: ls -l /opt/deploy로 문제 파일의 그룹 소유자를 봅니다 — deploy가 아니라 개인 그룹이면 상속이 안 되는 것입니다. ls -ld /opt/deploy로 그룹 자리에 s(setgid)가 있는지, 그리고 파일 권한에 그룹 쓰기(rw)가 있는지 확인합니다.
해결: 디렉터리에 setgid를 걸고(chmod 2775 /opt/deploy) 기존 파일의 그룹을 정리합니다(chgrp -R deploy /opt/deploy, 필요하면 chmod -R g+w). 공유 작업자는 umask를 002로 맞추거나 기본 ACL(setfacl -d -m g:deploy:rwx /opt/deploy)을 걸어 새 파일이 처음부터 그룹 쓰기로 태어나게 합니다. 마지막으로 방금 그룹에 추가된 사람은 재로그인해야 새 그룹이 적용됩니다.
실제 업무에서 이 지식이 쓰이는 상황:
현업 인프라 팀에서는 서비스마다 전용 계정을 두는 것이 표준입니다. Nginx는 nginx 계정, Tomcat은 tomcat 계정, PostgreSQL은 postgres 계정으로 실행합니다. 이 분리가 안 된 서버는 보안 감사를 통과하지 못합니다.
서버 초기화 스크립트 패턴 (실제 현장에서 쓰는 방식):
APP_USER="appuser"
APP_GROUP="appgroup"
APP_HOME="/opt/app"
if ! id "$APP_USER" &>/dev/null; then
sudo groupadd "$APP_GROUP"
sudo useradd -r -s /sbin/nologin -d "$APP_HOME" -g "$APP_GROUP" -m "$APP_USER"
fi
sudo chown -R "$APP_USER:$APP_GROUP" "$APP_HOME"
sudo chmod 750 "$APP_HOME"
sudo chmod 640 "$APP_HOME"/config/*.conf
sudo chmod 770 "$APP_HOME"/logs/
보안 사고 사례: 2021년 Log4Shell(CVE-2021-44228) 취약점이 발견됐을 때, root로 실행 중이던 WAS는 즉각 서버 전체 장악으로 이어졌습니다. 서비스 계정으로 격리된 서버는 피해가 /opt/app 내로 제한됐습니다. 계정 분리 하나가 사고의 반경을 결정합니다.
명령어·단축키 빠른 참조
사용자·그룹·권한 관리와 서비스 계정 격리에서 다룬 명령을 모았습니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
useradd | 일반·서비스 계정 생성 | useradd -r -s /sbin/nologin -d /opt/app -m appuser |
usermod | 계정 수정(그룹·잠금·셸) | usermod -aG appgroup appuser, usermod -L devuser |
groupadd / gpasswd | 그룹 생성·멤버 관리 | groupadd appgroup, gpasswd -a appuser appgroup |
id / getent | 계정 UID·GID·그룹 확인 | id appuser, getent passwd appuser |
/etc/passwd·/etc/shadow | 계정·패스워드 상태 확인 | grep appuser /etc/passwd, sudo grep appuser /etc/shadow(!=잠금) |
chmod(숫자) | 파일 권한 설정 | chmod 640 config.yml, chmod 600 secret.key |
chmod 2775 / g+s | 디렉터리 setgid(그룹 상속) | chmod 2775 /opt/deploy |
chown / chgrp | 소유자·그룹 변경 | chown -R appuser:appgroup /opt/app, chgrp -R deploy dir |
stat -c | 권한 숫자·rwx 동시 확인 | stat -c '%n: %a (%A)' file |
find / -perm -4000 | SUID 파일 감사 | find / -perm -4000 -type f 2>/dev/null |
visudo -f | sudoers 안전 편집(전용 파일) | visudo -f /etc/sudoers.d/appuser |
sudo -l | 계정 sudo 권한 점검 | sudo -l, sudo -l -U appuser |
newgrp | 새 그룹 즉시 적용(재로그인 대체) | newgrp deploy |
journalctl _COMM=sudo | sudo 실행 감사 로그 | journalctl _COMM=sudo --since today | grep appuser |
관련 모듈로 더 깊이:
- Linux 핵심 파일시스템 구조와 권한(chmod) 체계 완벽 요약 — 권한 체계의 토대인 파일시스템 구조와 chmod 기본
- SSH 접속 보안 강화와 배스천 호스트 기반 접근 제어 — 계정을 만든 다음 그 계정의 접속을 SSH 키로 통제하는 법
- 계정 권한 관리, 보안 헤더, TLS 강화 실무 — 서비스 계정 분리를 보안 운영 차원에서 강화하는 법
다음 모듈에서는 SSH 키 기반 인증과 서버 접근 제어 설정을 다룹니다.