팀에 새 개발자가 합류했습니다. 온보딩 첫날, 그에게 서버 접근 권한을 줘야 합니다. root 계정을 공유하면 편하지만 누가 무엇을 했는지 추적이 안 됩니다. 개인 계정을 만들어주려니 어떤 그룹에 넣어야 할지, sudo는 어디까지 줄지 판단이 서지 않습니다. 결국 sudo 그룹을 통째로 줬는데 — 3일 후 그가 실수로 rm -rf /etc/nginx/ 를 실행했습니다.
사용자 계정과 그룹을 제대로 설계하면 "이 사람은 이 명령만 실행할 수 있다"를 정밀하게 제어할 수 있습니다.
사용자와 그룹 관리
- 1/etc/passwd, /etc/shadow, /etc/group 파일 구조를 읽고 UID/GID 범위를 설명할 수 있다
- 2useradd, usermod, userdel로 사용자 계정을 생성하고 관리할 수 있다
- 3기본 그룹과 보조 그룹의 차이를 이해하고 usermod -aG로 그룹을 추가할 수 있다
- 4sudo와 su의 차이를 이해하고 /etc/sudoers를 visudo로 안전하게 편집할 수 있다
- 5최소 권한 원칙을 적용한 서비스 전용 계정을 설계할 수 있다
id && whoami && groupsgetent passwd | tail -5직접 /etc/sudoers를 편집하면 문법 오류 시 sudo가 잠길 수 있습니다
/etc/passwd · /etc/shadow · /etc/group 파일 구조
서버에서 특정 사용자가 갑자기 로그인이 안 된다는 제보가 들어왔을 때, 또는 새로 만든 계정이 docker 명령을 못 쓴다고 할 때 — 첫 번째로 확인해야 할 것이 이 세 파일입니다. Linux는 사용자 정보를 데이터베이스가 아닌 텍스트 파일로 관리하고, 각 파일이 다른 역할을 담당합니다. 이 파일들을 직접 읽을 줄 알면, id 명령 출력이 왜 그렇게 나오는지, 그룹 추가가 왜 바로 반영되지 않는지, 패스워드가 어디에 어떤 형태로 저장되는지를 모두 직접 확인할 수 있습니다.
확대
Linux의 사용자 정보는 세 개의 텍스트 파일에 나뉘어 저장됩니다.
/etc/passwd — 사용자 계정 데이터베이스
모든 사용자(사람 + 시스템 계정)가 한 줄씩 기록됩니다.
username:x:UID:GID:comment:home_dir:shell
실제 예시를 보면 다음과 같습니다.
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
deploy:x:1001:1001:서비스 배포 계정:/home/deploy:/bin/bash
UID 범위별 역할
UID 숫자 범위에 따라 계정의 유형이 구분됩니다. 이 범위를 알면 낯선 시스템에서 /etc/passwd를 봤을 때 어떤 계정이 사람용이고 어떤 게 데몬용인지 바로 판별할 수 있습니다.
| 범위 | 유형 | 설명 | 판단 기준 |
|---|---|---|---|
| 0 | root | 시스템의 절대 관리자 — 모든 파일과 프로세스에 접근 가능 | 프로세스가 UID 0으로 실행 중이면 즉시 확인 필요 |
| 1 – 999 | 시스템 계정 | 데몬·서비스용 계정. 로그인 불가(nologin shell)가 일반적 | shell이 /bin/bash면 비정상 — /sbin/nologin이어야 정상 |
| 1000+ | 일반 사용자 | 실제 사람이 로그인하는 계정. Ubuntu 기준 1000부터 시작 | 퇴직자 계정이 목록에 남아있으면 즉시 userdel 또는 usermod -L |
조합 해석 — 이 두 가지가 동시에 보이면:
- 시스템 계정(UID 1~999)인데 shell이
/bin/bash+ 최근 로그인 기록 있음 → 계정 탈취 또는 설정 오류 의심,last <계정명>으로 로그인 이력 즉시 확인 - 일반 계정인데
sudo그룹 + 퇴사 3개월 경과 → 접근 통제 누락,usermod -L <계정명>으로 잠금
두 번째 필드가 x인 것에 주목하세요. 실제 패스워드 해시는 여기에 없습니다.
/etc/shadow — 패스워드 해시 분리 저장
과거에는 패스워드 해시가 /etc/passwd에 직접 들어 있었습니다. 이 파일은 누구나 읽을 수 있기 때문에 오프라인 크래킹 공격에 노출됐습니다. 현대 Linux는 해시를 /etc/shadow로 분리하고, root만 읽을 수 있도록 권한을 640 또는 000으로 제한합니다.
username:$hash:last_change:min:max:warn:inactive:expire:reserved
deploy:$6$rounds=656000$salt$longhashstring...:19800:0:99999:7:::
각 필드의 의미입니다.
$6$...— SHA-512 해시 (알고리즘 번호 6)19800— 마지막 패스워드 변경일 (1970-01-01 기준 일수)99999— 패스워드 최대 유효 기간 (일)7— 만료 7일 전부터 경고
보안 원칙:
/etc/shadow는 절대 직접 편집하지 마세요.passwd,chage,usermod명령을 사용하면 형식 오류 없이 안전하게 수정할 수 있습니다.
/etc/group — 그룹 멤버십
그룹 정보는 /etc/group 파일에 저장됩니다. 각 줄이 하나의 그룹이며, 쉼표로 구분된 사용자 목록이 마지막 필드에 옵니다.
groupname:x:GID:member1,member2,...
docker:x:998:alice,deploy
sudo:x:27:alice
사용자는 하나의 **주 그룹(primary group)**과 여러 개의 **보조 그룹(supplementary group)**에 속할 수 있습니다. 파일에 접근할 때 커널은 이 목록 전체를 확인합니다.
확대
로그인하면 실제로 무슨 일이 — 사용자 인증·식별 6단계
새 계정을 만들었는데 SSH 로그인이 "안 되고", 비번은 맞는데 계정이 잠겼다 하고, 서비스 계정은 인증이 되는데도 접속하자마자 끊깁니다. 이 증상들은 전부 로그인 한 번에 순서대로 일어나는 조회 → 인증 → 자격증명 부여 → 셸 실행의 어느 한 단계가 막힌 것입니다. 로그인은 "비번이 맞나"만 보는 게 아니라, 앞서 본 /etc/passwd·/etc/shadow·/etc/group 세 파일을 차례로 거쳐 UID/GID라는 정체를 세션에 새기는 과정입니다. 이 흐름을 알면 "로그인이 안 된다"를 단계로 좁혀 진단할 수 있습니다.
[입력] 로그인: 사용자명 + 비밀번호 (login / sshd / su / sudo)
│
① 조회: 사용자명으로 /etc/passwd 검색 → UID·주 GID·홈·셸 확인
│
② 인증: 입력 비번을 해싱해 /etc/shadow 의 저장 해시와 대조 (PAM)
│ └─ 동시에 faillock(실패 누적 잠금)·pwquality·계정 만료도 검사
│
③ 자격증명: 인증 성공 → 세션 프로세스에 신원 부여
│ UID · 주 GID(/etc/passwd) + 보조 그룹 목록(/etc/group)
│
④ 환경: HOME=/home/<사용자> 로 이동, 로그인 셸(/etc/passwd 7번째 필드) 실행
│
⑤ 초기화: 셸이 /etc/profile·~/.bash_profile 읽고 프롬프트 표시
▼
[세션] 이후 이 세션의 모든 프로세스가 ③의 UID/GID로 파일 접근·권한을 판정
각 단계에서 무슨 일이 일어나고, 막히면 어떤 증상인가:
| 단계 | 하는 일 | 여기서 막히면 |
|---|---|---|
| ① 조회 | /etc/passwd에서 사용자명 → UID·GID·홈·셸. 이름은 라벨일 뿐, 진짜 정체는 UID 숫자 | 계정 없음 또는 UID 충돌 → 로그인 실패, 혹은 남이 남긴 파일이 내 소유로 보임 |
| ② 인증 | PAM이 입력 비번을 같은 알고리즘($6$=SHA-512)으로 해싱해 /etc/shadow와 비교 | 비번 오류 → Authentication failure / 실패 누적 → Account locked(faillock) |
| ③ 자격증명 | 세션에 UID·주 GID·보조 그룹을 세트 — 파일 권한 판정의 기준이 여기서 고정됨 | usermod -aG 후 재로그인 안 하면 이 단계가 옛 그룹으로 굳어 새 그룹 미반영 |
| ④ 셸 실행 | /etc/passwd 7번째 필드의 로그인 셸 실행 | 셸이 /sbin/nologin인 서비스 계정에 SSH → 인증은 되고도 즉시 끊김 |
| ⑤ 초기화 | 로그인 셸이 /etc/profile·~/.bash_profile을 읽고 프롬프트 | 홈 미생성(-m 누락) → ~가 /를 가리키거나 기본 프롬프트로 뜸 |
즉 "로그인이 안 된다"는 이 다섯 관문 중 하나가 막힌 것입니다. 비번 오류는 ②, 계정 잠금은 ②의 faillock, nologin 즉시 끊김은 ④, 그룹 변경이 반영 안 되는 것은 ③(재로그인 필요), 홈이 없어 생기는 문제는 ⑤입니다. 그래서 id <사용자>로 ③의 결과(UID·그룹)를, getent passwd <사용자>로 ①·④의 값(홈·셸)을 확인하면 어느 관문에서 막혔는지 바로 좁혀집니다.
사용자·그룹 관리 명령 — useradd · usermod · id
파일을 직접 편집하는 대신, 다중 사용자 환경을 구축하고 권한을 분리할 때 쓰는 표준 명령이 있습니다. 이 명령들이 결국 위의 세 파일을 안전하게 갱신합니다 — 손으로 /etc/passwd를 고치다 한 글자 틀리면 로그인이 막히지만, 명령은 일관성을 보장합니다.
- useradd --create-home (또는 -m):
사용자를 새로 생성할 때 홈 디렉토리를 함께 자동 생성합니다. 이 옵션이 없으면 사용자는 로그인 시 홈 디렉토리가 없어 루트 디렉토리
/나 갈 곳 없는 환경에 놓입니다.로컬 터미널$ sudo useradd --create-home alice - usermod -aG:
기본 그룹을 유지하면서 보조 그룹(예:
sudo,developers)에 추가합니다.-a(append)가 누락되면 기존 보조 그룹 정보가 전부 덮어써지는 사고가 납니다 — 반드시-aG로 씁니다.로컬 터미널$ sudo usermod -aG sudo alice - id:
사용자의 현재 보안 식별자(
uid,gid, 소속groups)를 출력하는 검증 도구입니다. 변경 후 반영 여부를 확인할 때 씁니다.로컬 터미널$ id alice uid=1001(alice) gid=1001(alice) groups=1001(alice),27(sudo)
sudo vs su — 권한 위임의 두 가지 방법
확대
배포 서버에 접속해서 Nginx를 재시작해야 하는데, 현재 계정으로는 권한이 없습니다. 이 상황에서 두 가지 선택지가 있습니다. root로 완전히 전환하거나, 이 명령 하나만 권한 상승해서 실행하거나. 전자가 su, 후자가 sudo입니다. 실무에서는 대부분 sudo가 더 안전한 선택입니다. 실행 내역이 로그에 남고, 권한 범위를 명령 단위로 제한할 수 있기 때문입니다. su는 여러 명령을 연속으로 실행해야 할 때나 root 환경이 필요한 경우에 씁니다.
su — 사용자 전환 (Switch User)
su는 다른 사용자로 완전히 전환합니다. 대상 계정의 패스워드가 필요합니다.
# root로 전환 (root 패스워드 필요)
su -
# 특정 사용자로 전환 (해당 사용자 패스워드 필요)
su - deploy
# 전환 없이 단일 명령만 실행
su - deploy -c "systemctl status myapp"
- (하이픈)는 중요합니다. 이것이 있으면 대상 사용자의 환경변수(PATH, HOME 등)까지 완전히 불러옵니다. 없으면 현재 환경을 유지한 채 사용자만 바뀌어 예상치 못한 동작이 생길 수 있습니다.
sudo — 특정 명령에 권한 위임 (Superuser Do)
sudo는 현재 사용자가 자신의 패스워드로 인증한 뒤 지정된 명령을 다른 사용자(보통 root) 권한으로 실행합니다. 모든 실행 내역이 /var/log/auth.log 또는 /var/log/secure에 기록됩니다.
# root 권한으로 단일 명령 실행
sudo apt update
# root 쉘로 진입
sudo -s
# 다른 사용자 권한으로 실행
sudo -u deploy ls /home/deploy
# 현재 계정에 부여된 sudo 권한 확인
sudo -l
/etc/sudoers 구조
visudo 명령으로만 편집하세요. 문법 검사를 자동으로 수행해 파일이 망가지는 것을 막아줍니다.
# 기본 문법
WHO WHERE=(AS_WHOM) WHAT
# alice는 어디서든 root로 모든 명령 실행 가능
alice ALL=(ALL) ALL
# deploy는 패스워드 없이 systemctl restart myapp만 실행 가능
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp
두 방법의 차이를 한 표로 정리하면 어떤 상황에서 무엇을 써야 할지 명확해집니다.
| 방법 | 패스워드 | 감사 로그 | 권한 범위 |
|---|---|---|---|
su | 대상 계정 패스워드 | 없음 | 계정 전체 권한 |
sudo | 본인 패스워드 | /var/log/auth.log | sudoers에 정의된 명령만 |
실습
실습 전 디렉토리와 예제 파일을 먼저 준비합니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part1/exam_1 && cd /tmp/linux/part1/exam_1
이제 실습을 진행합니다.
시스템에서 자신이 누구인지, 어떤 그룹에 속하는지 확인하는 것부터 시작합니다.
# 현재 사용자의 UID, GID, 그룹 목록 출력
id
# 출력 예시
uid=1000(alice) gid=1000(alice) groups=1000(alice),4(adm),27(sudo),998(docker)
# 사용자 이름만 출력
whoami
# 속한 그룹 이름 목록만 출력
groups
id 출력에서 27(sudo)가 보이면 sudo 권한이 있는 계정입니다. 998(docker)가 있으면 sudo 없이 docker 명령을 실행할 수 있습니다.
확인 포인트: /etc/passwd에서 자신의 계정 항목을 직접 찾아보세요.
grep "^$(whoami):" /etc/passwd
id && whoami && groups- 읽기 순서: id 출력은 uid → groups 순으로 확인한다. uid=0 이면 root, 1000+ 이면 일반 사용자. groups에 sudo/wheel/docker 가 있는지 바로 확인한다
- id 결과에 sudo 또는 wheel 그룹이 포함되면 sudo 권한이 있는 계정이다
- 조합 해석 — sudo 그룹 + docker 그룹 동시에 있으면: 이 계정은 사실상 root 수준 권한이다. docker run --rm -v /:/host 같은 명령으로 호스트 전체를 읽을 수 있기 때문이다
- grep "^$(whoami):" /etc/passwd 결과 마지막 필드(7번째)가 쉘 경로다 — /sbin/nologin 이면 로그인 불가 계정, /bin/bash 이면 로그인 가능
- groups 명령으로 현재 계정이 속한 그룹 목록이 공백 구분으로 출력된다
애플리케이션 배포에 사용할 전용 계정을 만듭니다. 개인 계정이 아니라 역할 중심 계정을 사용하면 퇴직자 처리와 감사 로그 추적이 훨씬 쉬워집니다.
# 실습 디렉토리 준비
mkdir -p /tmp/linux/part1/exam_1 && cd /tmp/linux/part1/exam_1
# 배포 전용 계정 생성 (-m: 홈 디렉토리 생성, -s: 로그인 쉘 지정)
sudo useradd -m -s /bin/bash deploy
# 생성 확인
id deploy
# uid=1002(deploy) gid=1002(deploy) groups=1002(deploy)
# 홈 디렉토리 생성 확인
ls -la /home/deploy/
# 계정 정보를 /etc/passwd에서 확인
getent passwd deploy
최소 권한 원칙: 그룹은 실제로 필요한 것만 부여하세요.
sudo그룹 대신 아래 실습 4에서 다루는 특정 명령만 허용하는 sudoers 설정이 더 안전합니다.
sudo useradd -m -s /bin/bash -G docker,sudo deploy- id deploy 출력에서 uid=1002(deploy) gid=1002(deploy) groups=1002(deploy) — uid가 1000+ 이면 일반 사용자 계정으로 올바르게 생성된 것이다
- ls -la /home/deploy/ 결과에 total 24 이상이고 .bashrc .profile 파일이 보이면 홈 디렉토리와 기본 파일이 정상 생성된 것이다
- getent passwd deploy 결과 마지막 필드가 /bin/bash 이면 로그인 가능한 쉘이 올바르게 설정된 것이다 — /sbin/nologin 이면 SSH 로그인 불가
이미 존재하는 계정에 그룹을 추가할 때는 usermod -aG를 사용합니다. -a (append) 플래그를 반드시 붙여야 합니다. 빠뜨리면 기존 보조 그룹이 모두 제거됩니다.
# 현재 사용자를 docker 그룹에 추가
sudo usermod -aG docker $USER
# 그룹 추가 확인 (재로그인 없이 현재 세션 확인)
groups
# 아직 docker가 없을 수 있음 — 새 세션에서 적용됨
# 새 그룹으로 즉시 전환 (재로그인 없이)
newgrp docker
# 확인
groups
# ... docker ...
# deploy 계정에도 그룹 추가
sudo usermod -aG docker deploy
id deploy
sudo usermod -aG docker $USER- newgrp docker 실행 후 groups 출력에 docker 가 포함되면 현재 세션에 그룹이 즉시 적용된 것이다
- id deploy 출력에서 groups=1002(deploy),998(docker) 처럼 docker가 보이면 그룹 추가 성공 — 보이지 않으면 usermod -aG 대신 usermod -G (덮어쓰기)를 실행한 것
- 재로그인 없이 docker 명령이 필요하면 newgrp docker, 다음 로그인부터 자동 적용이 목적이면 재로그인으로 충분하다
deploy 계정이 패스워드 없이 systemctl restart myapp만 실행할 수 있도록 설정합니다. sudo 그룹 전체 권한보다 훨씬 안전한 방식입니다.
# visudo로만 편집 — 직접 편집하면 실수 시 sudo 자체가 잠김
sudo visudo
파일 끝에 다음 줄을 추가합니다.
# deploy 계정은 myapp 재시작만 가능 (패스워드 불필요)
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp, /bin/systemctl status myapp
저장 후 테스트합니다.
# deploy 계정으로 전환하여 테스트
sudo -u deploy sudo systemctl status myapp
# 다른 명령은 거부되어야 함
sudo -u deploy sudo apt update
# Sorry, user deploy is not allowed to execute '/usr/bin/apt update' as root
sudo visudo- sudo -u deploy sudo systemctl status myapp 이 패스워드 입력 없이 실행되면 NOPASSWD 설정이 올바르게 적용된 것이다
- 허용되지 않은 명령 실행 시 'Sorry, user deploy is not allowed to execute' 메시지가 나와야 정상 — 이 메시지가 없고 실행된다면 sudoers 설정이 더 넓게 열려있다는 뜻
- visudo 저장 시 문법 오류가 있으면 'parse error' 경고와 함께 재편집을 유도한다 — 직접 vi /etc/sudoers 로 편집하면 이 검증이 없어 sudo 전체가 잠길 수 있다
서비스 계정에 초기 패스워드를 설정하고, 만료 정책을 검토합니다.
# 패스워드 설정
sudo passwd deploy
# 패스워드 만료 정책 확인
sudo chage -l deploy
# Last password change : Jan 15, 2024
# Password expires : never
# Password inactive : never
# Account expires : never
# Minimum number of days between password change : 0
# Maximum number of days between password change : 99999
# Number of days of warning before password expires : 7
# 보안 정책 설정 예시: 90일마다 변경, 만료 7일 전 경고
sudo chage -M 90 -W 7 deploy
# 계정 만료일 설정 (특정 날짜 이후 사용 불가)
sudo chage -E 2025-12-31 deploy
서비스 계정 패스워드 정책: 사람이 로그인하지 않는 서비스 계정은 SSH 키 인증만 허용하고 패스워드 로그인을 비활성화하는 것이 더 안전합니다.
sudo passwd deploy && sudo chage -l deploy- sudo chage -l deploy 결과에서 'Maximum number of days between password change: 99999' 이면 만료 정책이 없는 상태 — 90일 정책 적용 후 90으로 바뀌어야 정상
- Account expires 필드가 'never' 이면 계정 만료 없음, 날짜가 표시되면 해당 일 이후 로그인 불가 — 퇴직 예정자는 반드시 만료일 설정
- 판단 기준 — Password expires: never + 일반 사용자 계정 조합이면 보안 정책 미적용 상태다. 서비스 계정은 never가 허용되지만 사람 계정은 최대 90일(chage -M 90) 적용 권장
배포 스크립트나 CI/CD 파이프라인에서 특정 서비스 계정의 환경으로 명령을 실행하는 방법입니다.
# deploy 계정의 환경에서 단일 명령 실행
sudo su - deploy -c "whoami && pwd && env | grep HOME"
# 인터랙티브 쉘로 전환
sudo su - deploy
# 전환 후 확인
id
pwd # /home/deploy
배포 자동화에서는 sudo su - deploy -c가 더 예측 가능한 환경을 제공합니다. PATH와 환경변수가 완전히 초기화되기 때문입니다.
sudo su - deploy -c 'systemctl status myapp'- whoami 출력이 deploy 이고 pwd 출력이 /home/deploy 이면 deploy 계정의 환경으로 완전히 전환된 것이다
- env | grep HOME 결과가 HOME=/home/deploy 이면 홈 환경변수까지 정상 초기화된 것이다 — HOME=/root 이면 su - 의 -l 플래그가 빠진 것
- su - deploy (대시 있음)와 su deploy (대시 없음)의 차이: 대시 있으면 환경변수 완전 초기화, 없으면 이전 사용자 환경 일부 유지 — CI/CD 스크립트에서는 반드시 대시 있음 사용
PAM 기반 계정 잠금 & 패스워드 품질 정책 (pam_faillock / pwquality)
확대
비밀번호가 1234인 계정이 프로덕션 서버에 있다면, 브루트포스 공격에 몇 초도 안 걸립니다. 반대로 로그인 실패가 일정 횟수를 넘으면 자동으로 잠기는 정책이 있다면 공격 가능성이 크게 줄어듭니다. PAM은 이런 보안 정책을 시스템 전반에 일관되게 적용하는 Linux의 인증 프레임워크입니다. 패스워드 복잡도 요구사항, 로그인 실패 시 계정 잠금 — 이 두 가지가 기본 방어선이고, 여기서 다루는 설정이 그 구현입니다.
패스워드 관련 보안 정책은 PAM(Pluggable Authentication Modules)을 통해 시스템 전반에 일관되게 적용합니다. 설정 파일 위치는 배포판마다 다릅니다.
패스워드 품질 정책 (pwquality):
pwquality는 패스워드를 설정할 때 길이, 대소문자, 숫자, 특수문자 조건을 강제합니다. /etc/security/pwquality.conf에 정책을 정의하면 passwd 명령 실행 시 자동으로 적용됩니다.
# /etc/security/pwquality.conf 설정 예
sudo tee /etc/security/pwquality.conf << 'EOF'
minlen = 12 # 최소 12자
dcredit = -1 # 숫자 최소 1개 이상
ucredit = -1 # 대문자 최소 1개 이상
lcredit = -1 # 소문자 최소 1개 이상
ocredit = -1 # 특수문자 최소 1개 이상
maxrepeat = 3 # 같은 문자 3번 연속 금지
usercheck = 1 # 사용자명 포함 금지
EOF
# 설정 테스트 (패스워드 품질 점수 확인)
pwscore
# 입력: 12345 → 점수: 0 (너무 약함)
# 입력: P@ssw0rd123! → 점수: 100 (강함)
로그인 실패 잠금 정책 (pam_faillock):
pam_faillock은 지정한 횟수 이상 로그인에 실패하면 계정을 일시 잠금합니다. 브루트포스 공격의 기본 방어선입니다.
# RHEL 8+ / Rocky / AlmaLinux — /etc/security/faillock.conf
sudo tee /etc/security/faillock.conf << 'EOF'
deny = 5 # 5회 실패 시 잠금
fail_interval = 120 # 2분 안에 실패 횟수 집계
unlock_time = 300 # 5분 후 자동 잠금 해제 (0이면 영구 잠금)
silent # 잠금 여부 숨기기 (보안 강화)
audit # 잠금 이벤트를 audit 로그에 기록
EOF
# Ubuntu 22.04+ — /etc/pam.d/common-auth 확인
grep pam_faillock /etc/pam.d/common-auth
# 현재 잠긴 계정 확인
sudo faillock --user alice
# 계정 수동 잠금 해제 (관리자)
sudo faillock --user alice --reset
배포판별 PAM 설정 파일 위치:
Ubuntu와 RHEL 계열은 설정 파일 경로가 달라 혼동하기 쉽습니다. 서버 배포판을 확인한 뒤 해당 경로를 수정하세요.
| 배포판 | 패스워드 품질 | 로그인 실패 잠금 |
|---|---|---|
| RHEL 8+ / Rocky | /etc/security/pwquality.conf | /etc/security/faillock.conf |
| Ubuntu 22.04+ | pam_pwquality in /etc/pam.d/common-password | pam_faillock in /etc/pam.d/common-auth |
| Ubuntu 20.04 이하 | pam_cracklib (레거시) | pam_tally2 (deprecated) |
운영 주의: PAM 설정을 잘못 적용하면 모든 사용자가 로그인 불가 상태가 됩니다. 변경 전 반드시 root 세션을 별도 유지하고,
pam_config --service=sshd --query또는pamtester sshd <user> authenticate로 검증하세요.
자주 발생하는 오류
상황: useradd deploy 로 계정을 만들고 SSH 키도 등록했는데 ssh deploy@server 에서 위 오류가 납니다. 로그인 후 ~ 가 /root 를 가리키는 경우도 있습니다.
원인: useradd 시 -m 옵션을 빠뜨리면 홈 디렉토리가 생성되지 않습니다. .ssh/authorized_keys 를 저장할 위치가 없어 공개키 인증이 실패합니다.
진단:
# 홈 디렉토리 존재 확인
ls -la /home/deploy
ls: cannot access '/home/deploy': No such file or directory
해결:
계정을 삭제하고 재생성합니다. userdel 은 계정 데이터베이스만 삭제하지만, -r 옵션 추가 시 홈 디렉토리와 메일 스풀까지 함께 삭제되므로 주의하세요.
계정 삭제 + 홈 디렉토리 삭제 — 복구 불가
안전한 실행 조건: ls -la /home/deploy 로 삭제해도 되는 계정인지 확인하고, 중요 파일은 미리 백업한 후 실행
실행 전 반드시 확인
- 삭제 대상 계정이 실행 중인 프로세스가 없는지 ps aux | grep deploy 로 확인
- /home/deploy 안에 보존해야 할 파일이 없는지 ls -la /home/deploy 로 확인
- 아래 예시에서는 -r 없이 userdel만 사용 (홈 디렉토리는 유지)
sudo userdel -r deploy위 항목을 모두 확인한 후 복사할 수 있습니다
# 방법 1: 계정 삭제 후 -m 옵션과 함께 재생성 (-r 없이 계정 레코드만 삭제)
sudo userdel deploy
sudo useradd -m -s /bin/bash deploy
# 방법 2: 기존 계정에 홈 디렉토리 수동 생성
sudo mkdir -p /home/deploy
sudo chown deploy:deploy /home/deploy
sudo chmod 755 /home/deploy
상황: 새로 만든 계정으로 sudo apt update 를 실행했더니 위 메시지가 뜹니다. 분명히 계정은 정상인데 sudo 가 안 됩니다.
원인: 계정이 sudo 그룹(Ubuntu/Debian) 또는 wheel 그룹(RHEL/CentOS)에 속하지 않거나, /etc/sudoers 에 해당 계정 항목이 없습니다.
진단:
# 현재 그룹 목록 확인
groups alice
alice : alice
sudo 또는 wheel 이 보이지 않으면 권한이 없는 것입니다.
해결:
# sudo 그룹에 추가 (Ubuntu/Debian)
sudo usermod -aG sudo alice
# wheel 그룹에 추가 (RHEL/CentOS)
sudo usermod -aG wheel alice
# 변경 후 alice 가 새 터미널 세션에서 재로그인해야 적용됩니다
# 즉시 확인하려면:
su - alice
sudo -l
상황: sudo usermod -G docker alice 로 docker 그룹을 추가했는데, 이후 alice 계정으로 docker ps 를 실행하니 위 오류가 납니다. 게다가 기존에 있던 sudo 권한도 사라졌습니다.
원인: -G 옵션은 보조 그룹 목록을 완전히 대체합니다. -a (append) 플래그 없이 사용하면 기존 보조 그룹이 모두 제거되고 지정한 그룹 하나만 남습니다.
진단:
# 현재 그룹 확인
id alice
uid=1001(alice) gid=1001(alice) groups=1001(alice),998(docker)
sudo, adm 등 기존 그룹이 사라진 것을 확인합니다.
해결:
# 올바른 방법: -a 플래그로 기존 그룹 유지하며 추가
sudo usermod -aG docker alice
# 복구: 제거된 그룹 재추가
sudo usermod -aG sudo,adm alice
# 확인
id alice
uid=1001(alice) gid=1001(alice) groups=1001(alice),4(adm),27(sudo),998(docker)
심화 — 커널은 이름을 모른다: 계정의 진짜 정체는 숫자다
심화: 리눅스는 사용자를 '이름'이 아니라 'UID(숫자)'로 다룬다
지금까지 alice, deploy처럼 이름으로 계정을 다뤘습니다. 하지만 커널과 파일시스템은 그 이름을 전혀 모릅니다. 내부에서는 오직 숫자(UID/GID)만 저장하고 비교합니다. 이 사실을 알면 '이름은 다른데 권한이 새는' 당혹스러운 사고들의 정체가 보입니다.
- 이름은 라벨, 숫자가 정체: 파일 소유권은 inode에 UID/GID 숫자로 박히고, 커널의 권한 검사도 이 숫자만 봅니다.
ls -l이 사용자 이름을 보여 주는 건 UID를/etc/passwd에서 되짚어 표시하는 편의일 뿐입니다. - 이름을 바꿔도 권한은 그대로:
usermod -l로 이름만 바꿔도 UID가 같으면 그 계정이 소유하던 파일·권한은 하나도 바뀌지 않습니다. 반대로 이름이 같아도 UID가 다르면 완전히 다른 주체입니다. - UID 재사용의 함정:
userdel은/etc/passwd에서 이름 항목만 지웁니다. 그 사용자가 남긴 파일은 여전히 옛 UID 소유로 디스크에 남고(고아 UID), 나중에 만든 새 계정이 같은 UID를 재할당받으면 그 옛 파일들을 소급 상속합니다 — 이름은 다른데 커널 눈엔 같은 사람. - 컨테이너·NFS로 넘어가면 더 선명: 컨테이너 안 UID나 다른 호스트의 UID가 볼륨/NFS를 통해 같은 숫자면 같은 권한으로 취급됩니다. 컨테이너 안 root(UID 0)가 마운트한 호스트 파일을 root로 만지는 이유가 이것입니다.
정리하면 계정을 '이름'으로만 생각하면 반은 놓칩니다. 안전한 계정 회수란 이름을 지우는 것이 아니라, 그 UID가 남긴 흔적까지 정리하는 것입니다.
상황: 퇴사한 alice(UID 1001) 계정을 userdel alice로 삭제했습니다(홈은 -r 없이 남겨 둠). 며칠 뒤 인턴 bob을 useradd bob으로 만들었습니다. bob이 처음 로그인해 둘러보니, /home/alice 아래 옛 파일들과 여기저기 alice가 만들어 둔 파일이 전부 bob 소유로 표시됩니다. alice만 접근하던 자료에 bob이 접근됩니다.
원인: 커널은 소유권을 이름이 아니라 UID 숫자로 판단합니다. userdel alice는 이름 항목만 지웠을 뿐, alice가 남긴 파일들은 여전히 UID 1001 소유로 남아 있었습니다. useradd bob이 다음 빈 UID로 하필 1001을 재할당하자, 커널 눈에는 'UID 1001 = bob'이므로 alice의 옛 파일 전부가 bob 것이 된 것입니다.
진단:
# bob에게 재할당된 UID 확인
id -u bob # 예: 1001
# 그 UID로 소유된 '옛 파일'이 bob의 홈 밖에도 남아 있는지
sudo find / -uid 1001 -not -path '/home/bob/*' 2>/dev/null | head
bob의 홈 밖에서 UID 1001 소유 파일이 쏟아지고, 그 UID가 최근 삭제한 alice의 것이었다면 UID 재사용 사고로 확정입니다.
해결:
# 1) 재사용된 UID가 상속한 옛 파일을 찾아 정리하거나 올바른 소유자로 이관
sudo find / -uid 1001 -not -path '/home/bob/*' 2>/dev/null
# 보존 대상은 아카이브, 불필요하면 삭제, 넘겨줄 파일은 chown 으로 재소유
# 2) 재발 방지: 계정 회수를 '이름'이 아니라 'UID' 기준으로
# 삭제 전에 sudo find / -uid 1001 로 잔여 파일을 먼저 목록화
# userdel -r 로 홈까지 지우거나, 남길 파일은 명시적으로 다른 소유자에게 chown
# 퇴사자 UID를 당분간 재발급 풀에서 제외
핵심 교훈: 계정 '삭제'는 이름을 지우는 일이고, 보안상 '회수'는 그 UID가 디스크에 남긴 흔적까지 지우는 일입니다. 이름과 숫자를 분리해서 봐야 이런 조용한 권한 누수를 막을 수 있습니다.
실무 시나리오
sudoers 최소 권한 운영 템플릿 — Cmnd_Alias 분리·NOPASSWD 안전 사용
확대
CI/CD 파이프라인에서 배포 스크립트가 sudo systemctl restart myapp을 실행해야 한다면, 해당 계정에 sudo 전체 권한을 주는 건 과합니다. root 권한으로 모든 명령을 실행할 수 있게 되기 때문입니다. sudoers의 Cmnd_Alias를 쓰면 "이 계정은 이 명령만, 패스워드 없이"를 정밀하게 설정할 수 있습니다. 실무에서 서비스 계정의 sudo 권한을 설계할 때 가장 많이 쓰는 패턴입니다.
sudoers 파일은 최소 권한 원칙(Least Privilege)을 적용해 꼭 필요한 명령만 허용해야 합니다.
Cmnd_Alias로 명령 그룹화:
Cmnd_Alias는 관련 명령을 하나의 이름으로 묶어 sudoers 파일을 읽기 쉽게 유지하고, 추후 명령 추가·제거를 한 곳에서 관리할 수 있게 합니다.
# /etc/sudoers.d/deploy (visudo -f /etc/sudoers.d/deploy로 편집)
# 명령 그룹 정의
Cmnd_Alias DEPLOY_CMDS = /bin/systemctl restart myapp, \
/bin/systemctl start myapp, \
/bin/systemctl stop myapp, \
/usr/local/sbin/deploy-sync
Cmnd_Alias LOG_CMDS = /usr/bin/journalctl, \
/bin/tail
# 배포 계정에 명령 그룹 허용 (패스워드 없이)
deploy ALL=(root) NOPASSWD: DEPLOY_CMDS
# deploy-sync는 root:root 소유·쓰기 불가(0755)로 두고
# 소스·대상·옵션을 코드에 고정합니다. /usr/bin/rsync를 직접 허용하면
# --rsync-path 등 옵션으로 임의의 root 명령 실행이 가능해집니다.
# 개발자는 로그만 볼 수 있음
%developers ALL=(root) NOPASSWD: LOG_CMDS
# 특정 IP에서만 NOPASSWD 허용 (내부 CI/CD 서버 IP)
cicd 10.0.1.0/24=(root) NOPASSWD: DEPLOY_CMDS
NOPASSWD 사용 시 감사 보강:
NOPASSWD를 허용하면 패스워드 확인이 없어 편리하지만 그만큼 감사 로그가 중요해집니다. auditd를 함께 구성하면 sudoers 파일 자체의 변경 이력도 추적할 수 있습니다.
# sudo 실행 내역은 /var/log/auth.log 또는 /var/log/secure에 자동 기록
grep "sudo" /var/log/auth.log | grep "COMMAND"
# sudo: deploy : ... COMMAND=/bin/systemctl restart myapp
# auditd로 더 세밀한 감사 (설치 필요)
sudo apt install auditd
# /etc/audit/rules.d/sudo.rules
# -w /etc/sudoers -p wa -k sudoers_change
# -w /etc/sudoers.d/ -p wa -k sudoers_change
sudo augenrules --load
# sudo 실행 내역 감사 로그 확인
sudo ausearch -k sudoers_change
sudoers 파일 안전 편집 규칙:
/etc/sudoers를 직접 편집하다가 문법 오류가 생기면 sudo 자체가 잠겨 root 접근이 불가능해질 수 있습니다. visudo는 저장 전에 문법을 검사해 이를 방지합니다.
# 반드시 visudo 사용 (문법 검증 포함)
sudo visudo
# 특정 파일 편집
sudo visudo -f /etc/sudoers.d/deploy
# 문법 오류 있으면 저장 거부 → 파일 손상 방지
# 절대 직접 편집하지 말 것: sudo vi /etc/sudoers ← 위험!
# 현재 자신의 sudo 권한 확인
sudo -l
명령어·단축키 빠른 참조
이 모듈에서 다룬 계정·그룹 관리와 권한 위임(su·sudo) 명령을 실전 옵션과 함께 모았습니다. "예" 열의 조합을 그대로 써도 됩니다.
| 명령어/단축키 | 용도 | 자주 쓰는 예 |
|---|---|---|
id / whoami / groups | 현재 사용자·그룹 확인 | id && whoami && groups |
su | 사용자 전환 | su -(root 전환), su - deploy |
sudo | 단일 명령 권한 위임 | sudo systemctl restart myapp, sudo -i(root 쉘) |
sudo -l | 내 sudo 권한 확인 | 어떤 명령이 허용됐는지 목록 |
useradd -m -s -G | 계정 생성 | sudo useradd -m -s /bin/bash -G docker,sudo deploy |
usermod -aG | 보조 그룹 추가 | sudo usermod -aG docker $USER (반드시 -a) |
newgrp | 재로그인 없이 새 그룹 적용 | newgrp docker (현재 세션에 즉시 반영) |
passwd | 패스워드 설정 | sudo passwd deploy |
chage -l | 패스워드 만료 정책 확인 | sudo chage -l deploy |
visudo | sudoers 안전 편집(문법 검사) | sudo visudo -f /etc/sudoers.d/deploy |
/etc/passwd·shadow·group | 계정·해시·그룹 저장 파일 | grep deploy /etc/passwd |
관련 모듈로 더 깊이:
- chmod/chown으로 파일 읽기·쓰기·실행 권한 완벽 제어 — 사용자·그룹이 파일에 대해 갖는 rwx 권한을 정밀 제어하는 다음 단계
- SSH 보안 설정과 서버 접속 하드닝 — 어떤 사용자가 어떤 키로 서버에 접속할지 연결하는 법
- CIS 벤치마크 기반의 리눅스 OS Hardening 및 취약점 방어 — 계정·sudo 정책을 서버 보안 강화의 일부로 설계하는 법
다음 모듈에서는 파일 권한(File Permissions) — chmod, chown, umask로 파일과 디렉토리에 대한 접근을 정밀하게 제어하는 방법을 다룹니다.