사용자 및 그룹 프로비저닝 자동화
useradd, chage 및 sudoers 조각 관리를 사용하여 계정을 일괄 생성, 수정 및 감사합니다.
사용자 및 그룹 프로비저닝 자동화은(는) CoddyKit의 무료 DevOps Bootcamp 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 DevOps Bootcamp 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
사용자 프로비저닝을 자동화해야 하는 이유
useradd를 사용해 한 번에 한 명씩 사용자를 관리하는 방식은 계정이 몇 개뿐일 때는 잘 작동합니다. 하지만 엔터프라이즈 환경에서는 일반적으로 수십 명 또는 수백 명의 사용자를 동시에 등록합니다. 수동 명령은 오류가 발생하기 쉽고 일관성이 없으며 감사도 어렵습니다.
Bash 스크립팅을 사용하면 다음을 수행할 수 있습니다.
- 매번 표준화된 설정(셸, 홈 디렉터리, 암호 정책)으로 사용자를 생성합니다.
- 신규 입사자 목록이 담긴 CSV 또는 텍스트 파일을 읽어 한 번에 프로비저닝합니다.
- 모든 작업을 기록하여 규정 준수를 위한 감사 추적을 남깁니다.
- 구성 관리 파이프라인(Ansible, Chef, Jenkins)과 통합합니다.
이 레슨에서는 useradd, chage, usermod, 그룹 관리, sudoers 드롭인, 실행 후 감사를 다루면서 처음부터 운영 환경 수준의 사용자 프로비저닝 스크립트를 작성합니다.
대량 사용자 목록 읽기
대량 프로비저닝의 표준 입력 형식은 구분자로 분리된 텍스트 파일이며, 한 줄에 하나의 레코드가 들어갑니다. 일반적인 CSV는 다음과 같습니다.
username,full_name,group,shell
alice,Alice Smith,developers,/bin/bash
bob,Bob Jones,ops,/bin/zshwhile 루프 안에서 IFS와 read를 사용해 안전하게 구문 분석합니다. tail -n +2로 헤더 줄을 건너뛰면 로직을 깔끔하게 유지할 수 있습니다.
주요 방어적 처리 방법:
- 각 필드의 앞뒤 공백을 제거합니다.
- 빈 줄과
#으로 시작하는 주석 줄을 건너뜁니다. - 시스템 명령을 호출하기 전에 필수 필드가 비어 있지 않은지 확인합니다.
#!/usr/bin/env bash
# parse_users.sh — safely read a CSV of users
set -euo pipefail
USER_FILE="${1:-users.csv}"
[[ -f "$USER_FILE" ]] || { echo "ERROR: $USER_FILE not found"; exit 1; }
tail -n +2 "$USER_FILE" | while IFS=',' read -r username full_name group shell; do
# trim whitespace
username="${username// /}"
[[ -z "$username" || "$username" == \#* ]] && continue
echo "Parsed -> user=$username group=$group shell=$shell"
doneuseradd로 사용자 생성하기
useradd는 /etc/passwd, /etc/shadow, /etc/group에 기록하는 저수준 유틸리티입니다. 스크립팅에 가장 중요한 옵션은 다음과 같습니다.
-m— 홈 디렉터리를 생성합니다.-s— 로그인 셸을 설정합니다.-c— GECOS 주석 필드(전체 이름)를 설정합니다.-G— 보조 그룹을 설정합니다(쉼표로 구분).-e— 계정 만료 날짜를 설정합니다(YYYY-MM-DD).
useradd를 호출하기 전에 항상 id로 사용자가 이미 존재하는지 확인하십시오. 기존 사용자에게 실행하면 종료 코드 9를 반환하고 오류를 출력하므로 로그가 불필요하게 복잡해질 수 있습니다.
참고: useradd를 실행하려면 root 권한이 필요합니다. 스크립트 앞부분에서 권한을 확인하도록 작성하십시오.
#!/usr/bin/env bash
# create_user.sh — idempotent single-user creation
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USERNAME="$1"
FULL_NAME="${2:-}"
GROUP="${3:-staff}"
SHELL="${4:-/bin/bash}"
if id "$USERNAME" &>/dev/null; then
echo "[SKIP] User $USERNAME already exists"
else
useradd \
--create-home \
--shell "$SHELL" \
--comment "$FULL_NAME" \
--groups "$GROUP" \
"$USERNAME"
echo "[OK] Created $USERNAME"
fi초기 암호를 안전하게 설정하기
스크립트에 암호를 절대 하드코딩하지 마십시오. 대량 프로비저닝에서 사용할 수 있는 안전한 방법은 다음 두 가지입니다.
- 무작위 초기 암호 생성:
openssl rand또는/dev/urandom으로 암호를 생성하고 한 번만 출력한 다음, 사용자가 처음 로그인할 때 암호를 변경하도록 강제합니다. - 미리 해시된 암호 설정: SHA-512 해시와 함께
usermod -p를 사용하여 평문 암호가 프로세스 목록에 나타나지 않게 합니다.
스크립트에서 암호를 설정할 때는 chpasswd를 사용하는 것이 좋습니다. 이 명령은 표준 입력에서 username:password 쌍을 읽으므로 암호가 명령줄 인수(ps로 표시 가능)에 나타나지 않습니다.
암호를 설정한 후에는 chage -d 0을 사용하여 다음 로그인 시 즉시 암호를 재설정하도록 강제하십시오.
#!/usr/bin/env bash
# set_temp_password.sh
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USERNAME="$1"
# Generate a 16-char random password (alphanumeric only)
TMP_PASS=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 16)
# Set password via chpasswd (password never in argv)
echo "${USERNAME}:${TMP_PASS}" | chpasswd
# Force password change on next login
chage -d 0 "$USERNAME"
echo "[OK] Temporary password for $USERNAME: $TMP_PASS"
echo "[OK] User must change password on first login"chage로 암호 만료 기간 관리하기
chage(change age)는 /etc/shadow에 저장된 암호 만료 기간 정책을 제어합니다. 엔터프라이즈 보안 정책에서는 일반적으로 다음을 요구합니다.
- 최대 암호 사용 기간(예: 90일)
- 암호를 다시 변경할 수 있을 때까지의 최소 기간
- 만료 전 경고 기간
- 마지막 암호 사용 후 계정 비활성 잠금
주요 chage 옵션:
-M <days>— 최대 암호 사용 기간-m <days>— 최소 암호 사용 기간-W <days>— 만료 전 경고 기간-I <days>— 계정 잠금 전 비활성 기간-E <date>— 절대 계정 만료일-l— 사용자의 현재 설정을 나열합니다.
#!/usr/bin/env bash
# apply_password_policy.sh — enforce org-wide ageing policy
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
# Policy constants
MAX_AGE=90
MIN_AGE=1
WARN_DAYS=14
INACTIVE_DAYS=30
apply_policy() {
local user="$1"
chage \
-M "$MAX_AGE" \
-m "$MIN_AGE" \
-W "$WARN_DAYS" \
-I "$INACTIVE_DAYS" \
"$user"
echo "[OK] Policy applied to $user"
}
# Apply to all non-system users (UID >= 1000)
awk -F: '$3 >= 1000 && $3 < 65534 { print $1 }' /etc/passwd | while read -r user; do
apply_policy "$user"
done대량 그룹 관리
그룹은 리소스 접근을 제어하는 기본 수단입니다. 프로비저닝 스크립트는 사용자를 그룹에 추가하기 전에 필요한 그룹이 존재하는지 확인해야 합니다. useradd -G nonexistent는 실패합니다.
종료 코드를 확인하여 groupadd를 멱등적으로 사용하십시오. 그룹이 이미 존재하면 9를 반환합니다. getent group <name> 관용구는 /etc/group에서 grep하는 것보다 이식성이 높고 읽기 쉽습니다.
gpasswd -a는 기존 그룹 소속을 대체하지 않고 사용자를 그룹에 추가합니다. 반면 usermod -G는 보조 그룹 목록을 대체합니다.
#!/usr/bin/env bash
# ensure_groups.sh — create groups if missing, then add users
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
REQUIRED_GROUPS=(developers ops security auditors)
for grp in "${REQUIRED_GROUPS[@]}"; do
if getent group "$grp" &>/dev/null; then
echo "[SKIP] Group $grp already exists"
else
groupadd "$grp"
echo "[OK] Created group $grp"
fi
done
# Safely add a user to a group (append, don't replace)
add_to_group() {
local user="$1" group="$2"
gpasswd -a "$user" "$group" 2>/dev/null && echo "[OK] $user -> $group"
}전체 대량 프로비저닝 스크립트
이제 모든 내용을 하나로 합쳐 보겠습니다. 하나의 스크립트가 CSV를 읽고, 사용자와 그룹을 생성하며, 암호 정책을 설정하고, 모든 작업을 기록하고, 전체 일괄 처리를 중단하지 않으면서 오류를 적절히 처리합니다.
아래 스크립트에서 중요한 설계 결정은 다음과 같습니다.
- 타임스탬프가 포함된
LOG_FILE로 모든 작업을 기록하여 감사에 활용합니다. - 개별 사용자에 대한 오류는 기록하지만 루프를 중단하지 않습니다(
|| log_error). - 스크립트는 멱등적이므로 일부 실패 후에도 안전하게 다시 실행할 수 있습니다.
tee를 통해 모든 출력을 터미널과 로그 파일 양쪽으로 보냅니다.
#!/usr/bin/env bash
# bulk_provision.sh — production user provisioning
set -uo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USER_FILE="${1:-users.csv}"
LOG_FILE="/var/log/user_provision_$(date +%F).log"
log() { echo "[$(date '+%F %T')] $*" | tee -a "$LOG_FILE"; }
err() { log "ERROR: $*"; }
log "=== Provisioning started from $USER_FILE ==="
tail -n +2 "$USER_FILE" | while IFS=',' read -r username fullname group shell; do
username="${username// /}"
[[ -z "$username" || "$username" == \#* ]] && continue
group="${group:-staff}"
shell="${shell:-/bin/bash}"
# Ensure group exists
getent group "$group" &>/dev/null || groupadd "$group"
# Create user idempotently
if id "$username" &>/dev/null; then
log "[SKIP] $username exists"
else
useradd -m -s "$shell" -c "$fullname" -G "$group" "$username" || { err "useradd failed for $username"; continue; }
TMP="$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 14)"
echo "${username}:${TMP}" | chpasswd
chage -M 90 -m 1 -W 14 -I 30 -d 0 "$username"
log "[OK] $username created (group=$group) tmp_pass=$TMP"
fi
done
log "=== Provisioning complete ==="sudoers 드롭인 조각 관리
/etc/sudoers를 직접 편집하는 것은 위험합니다. 구문 오류가 발생하면 모든 사용자가 sudo를 사용할 수 없게 됩니다. 안전한 방법은 /etc/sudoers.d/에 드롭인 파일을 사용하고, 배치하기 전에 각 파일을 visudo -c -f로 검증하는 것입니다.
sudoers 조각의 권장 방법:
- 권한을 부여하는 팀이나 역할의 이름을 따서 파일 이름을 지정합니다(예:
10-developers,20-ops). - 사용자별 줄 대신 그룹 기반 규칙(
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl)을 사용합니다. - 항상 권한을
0440으로, 소유자를root:root로 설정합니다. visudo -c로 검증합니다. 구문 오류가 있으면 0이 아닌 종료 상태를 반환합니다.
#!/usr/bin/env bash
# write_sudoers_fragment.sh
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
FRAGMENT_NAME="${1:-10-developers}"
SUDOERS_DIR="/etc/sudoers.d"
TMP_FILE="$(mktemp)"
# Write the fragment to a temp file first
cat > "$TMP_FILE" << 'EOF'
# Developers: restart services and view journals without full root
%developers ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *, /usr/bin/journalctl
%ops ALL=(ALL) NOPASSWD: ALL
EOF
# Validate BEFORE installing
if visudo -c -f "$TMP_FILE"; then
install -m 0440 -o root -g root "$TMP_FILE" "${SUDOERS_DIR}/${FRAGMENT_NAME}"
echo "[OK] Installed ${SUDOERS_DIR}/${FRAGMENT_NAME}"
else
echo "[ERROR] sudoers syntax check failed — fragment NOT installed"
rm -f "$TMP_FILE"
exit 1
fi
rm -f "$TMP_FILE"기존 계정 감사하기
프로비저닝 후에는 물론 정기적으로도 사용자 데이터베이스를 감사하여 이상 징후를 찾아야 합니다.
- UID 0 계정 — root 이외에 UID 0을 가진 계정은 모두 심각한 보안 문제입니다.
- 암호가 없는 계정 —
/etc/shadow에서 암호 필드가 비어 있거나!인 항목입니다. - 만료되었지만 여전히 활성 상태인 계정 —
chage -l출력을 대량으로 구문 분석할 수 있습니다. - 셸은 있지만 홈 디렉터리가 없는 사용자 — 로그인을 방해하는 잘못된 설정입니다.
구조화된 보고서를 생성하여 mail로 보안 팀에 이메일을 보내거나 모니터링되는 로그 경로에 추가하는 작업은 간단합니다.
#!/usr/bin/env bash
# audit_users.sh — produce a security-relevant user report
set -uo pipefail
REPORT="/var/log/user_audit_$(date +%F).txt"
echo "=== User Audit Report $(date) ===" > "$REPORT"
echo "" >> "$REPORT"
echo "--- Accounts with UID 0 (should be root only) ---" >> "$REPORT"
awk -F: '$3 == 0 { print $1 }' /etc/passwd >> "$REPORT"
echo "" >> "$REPORT"
echo "--- Accounts with empty password field ---" >> "$REPORT"
awk -F: '($2 == "" || $2 == "!") && $3 >= 1000 { print $1 }' /etc/shadow 2>/dev/null >> "$REPORT" || echo " (requires root)" >> "$REPORT"
echo "" >> "$REPORT"
echo "--- Normal users (UID 1000-60000) ---" >> "$REPORT"
awk -F: '$3 >= 1000 && $3 < 60000 { printf "%-20s uid=%-6s shell=%s\n", $1, $3, $7 }' /etc/passwd >> "$REPORT"
cat "$REPORT"
echo "Report saved to $REPORT"계정 잠금, 잠금 해제 및 제거
오프보딩은 온보딩만큼 중요합니다. 사용자가 퇴사하면 다음 순서로 처리해야 합니다.
- 계정 잠금 (
usermod -L) — shadow 암호 해시에!를 앞에 추가하여 데이터를 삭제하지 않고 로그인을 차단합니다. - sudo 권한 철회 — 해당 사용자의 sudoers 조각이 있으면 제거합니다.
- 소유권 이전 — 사용자의 파일 소유권을 관리자 또는 보관 계정으로 이전합니다.
- 보관 — 삭제하기 전에 홈 디렉터리를 tarball로 보관합니다.
- 삭제 —
userdel -r로 홈 디렉터리와 메일 스풀을 제거합니다.
usermod -U는 계정의 잠금을 해제하고(! 접두사를 제거), 임시 정지 후 다시 활성화할 때 유용합니다.
#!/usr/bin/env bash
# offboard_user.sh — lock, archive, then optionally delete
set -euo pipefail
[[ $EUID -ne 0 ]] && { echo "Must run as root"; exit 1; }
USERNAME="$1"
ARCHIVE_DIR="/srv/archived-homes"
mkdir -p "$ARCHIVE_DIR"
# 1. Lock account
usermod -L "$USERNAME"
echo "[OK] Account $USERNAME locked"
# 2. Remove sudoers fragment if present
SUDOERS_FILE="/etc/sudoers.d/${USERNAME}"
[[ -f "$SUDOERS_FILE" ]] && rm -f "$SUDOERS_FILE" && echo "[OK] Removed sudoers fragment"
# 3. Archive home directory
HOME_DIR="$(getent passwd "$USERNAME" | cut -d: -f6)"
if [[ -d "$HOME_DIR" ]]; then
tar -czf "${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz" -C "$(dirname "$HOME_DIR")" "$(basename "$HOME_DIR")"
echo "[OK] Home archived to ${ARCHIVE_DIR}/${USERNAME}_$(date +%F).tar.gz"
fi
echo "[NOTICE] Review archive, then run: userdel -r $USERNAME"시험 실행 모드로 스크립트 테스트하기
운영 환경의 프로비저닝 스크립트는 부작용 없이 테스트할 수 있어야 합니다. 모든 변경 명령을 echo 스텁으로 대체하는 DRY_RUN 플래그를 사용해 시험 실행 모드를 구현하십시오.
패턴은 간단합니다. 플래그에 따라 명령을 실행하거나 에코하는 run() 도우미를 정의합니다. 이 접근 방식을 사용하면 다음이 가능합니다.
- 테스트 중에 모든 코드 경로를 실행할 수 있습니다.
- 실제 실행에서 발생할 일을 출력에 정확히 표시합니다.
- CI 파이프라인이 root 권한 없이 로직을 검증할 수 있습니다.
시험 실행과 함께 전용 테스트 사용자 접두사(예: test_)를 사용하면 통합 테스트 후 쉽게 정리할 수 있습니다.
#!/usr/bin/env bash
# provision_with_dryrun.sh
set -euo pipefail
DRY_RUN="${DRY_RUN:-false}"
# Wrapper: execute or echo
run() {
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY-RUN] $*"
else
"$@"
fi
}
create_user() {
local user="$1" group="$2"
if id "$user" &>/dev/null; then
echo "[SKIP] $user exists"
return
fi
run useradd -m -s /bin/bash -G "$group" "$user"
run chage -M 90 -m 1 -W 14 -d 0 "$user"
echo "[OK] $user provisioned (dry=$DRY_RUN)"
}
# Test run
DRY_RUN=true create_user testuser developers
echo "---"
create_user realuser developers 2>/dev/null || true기존 그룹 소속을 제거하지 않고 사용자를 보조 그룹에 추가하려면 어떤 명령을 사용해야 합니까?
대량 프로비저닝 스크립트에서 기존 사용자를 auditors 그룹에 할당해야 합니다. 이 사용자는 이미 developers와 staff의 구성원입니다. 새 그룹을 추가하면서 기존의 모든 소속을 유지하는 명령은 무엇입니까?
레슨 복습: 사용자 및 그룹 프로비저닝 자동화
이 레슨에서는 운영 환경 수준의 완전한 사용자 프로비저닝 도구 모음을 구축했습니다. 핵심 내용은 다음과 같습니다.
- 입력을 방어적으로 구문 분석 —
IFS/read루프를 사용하고, 빈 줄과 주석 줄을 건너뛰며, 시스템 호출 전에 필드를 검증합니다. useradd핵심 사항 — 항상-m(홈),-s(셸),-c(주석),-G(그룹)를 사용하고, 멱등성을 위해 먼저id로 존재 여부를 확인합니다.- 암호 —
chpasswd의 표준 입력으로 설정하여 평문이 프로세스 인수에 포함되지 않게 하고,chage -d 0으로 첫 로그인 시 재설정을 강제합니다. - 정책에
chage사용 — 모든 비시스템 계정에 최대 사용 기간(-M), 경고 기간(-W), 비활성 잠금(-I)을 표준화합니다. - 그룹 소속 — 소속을 대체하지 않고 추가하려면
gpasswd -a또는-a옵션을 포함한usermod -aG를 사용합니다. - sudoers 드롭인 —
/etc/sudoers.d/에 작성하고, 설치 전에visudo -c -f로 검증하며,0440 root:root권한을 설정합니다. - 오프보딩 — 잠그고(
usermod -L), 홈 디렉터리를 보관한 다음 삭제합니다. 보관 단계를 절대 생략하지 마십시오. - 시험 실행 모드 — 변경 명령을
run()도우미로 감싸 파이프라인이 root 권한에 따른 부작용 없이 로직을 검증할 수 있게 합니다.
이러한 패턴을 결합하면 어떤 규모에서도 Linux ID 관리를 위한 반복 가능하고 감사할 수 있으며 안전한 자동화 계층을 구축할 수 있습니다.
자주 묻는 질문
“사용자 및 그룹 프로비저닝 자동화” 강의는 무료인가요?
네 — “사용자 및 그룹 프로비저닝 자동화” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 DevOps Bootcamp 강의 전체를 잠금 해제할 수 있습니다. DevOps Bootcamp 강의에는 총 4개의 강의가 포함되어 있습니다.
“사용자 및 그룹 프로비저닝 자동화”에서 뭘 배우나요?
useradd, chage 및 sudoers 조각 관리를 사용하여 계정을 일괄 생성, 수정 및 감사합니다. 브라우저에서 직접 실행하는 실습 코드로 DevOps Bootcamp을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
DevOps Bootcamp을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 DevOps Bootcamp은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“사용자 및 그룹 프로비저닝 자동화” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 DevOps Bootcamp 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 DevOps Bootcamp 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 사용자 및 그룹 프로비저닝 자동화
- systemd 서비스 제어 및 유닛 파일 작성
- 디스크, 파일 시스템 및 마운트 자동화
- 시스템 상태 점검 및 알림 스크립트 구축