설정은 다 맞는데 403이 난다 — 9할은 SELinux다

httpd 설정을 다 맞췄다. 파일도 /var/www/html에 넣었다. 그런데 브라우저에서 403 Forbidden. Apache 설정만 뒤지다 한참을 헤맨다 — 그런데 진짜 원인은 SELinux인 경우가 압도적으로 많다. 파일을 cp/mv로 옮기면 보안 레이블(컨텍스트)이 잘못되어 SELinux가 접근을 막는 것이다.

리눅스 보안은 다층 방어(defense in depth)다. 전통 권한(DAC) 위에 강제 접근 제어(SELinux), 네트워크 검문(firewalld), 인증(PAM)이 올려져 있고, 각 층이 독립적으로 통과돼야 접근이 허용된다. 이 장을 모르면 "설정 다 맞는데 왜 안 되지?"의 원인을 못 찾고, 방화벽·인증 정책도 못 짠다. 이 글은 그 다층 구조를 다룬다.

접근 제어의 두 층 — DAC와 MAC, 둘 다 통과해야 한다

보안 층 건물 비유 리눅스
DAC(권한 rwx) 출입증 소유자가 자기 사무실 출입 권한 부여 전통 UNIX 퍼미션(05장)
MAC(SELinux) 건물 보안 정책이 출입증과 무관하게 추가 검문 SELinux
firewalld 건물 정문 검문대(외부인 통제) 네트워크 패킷 필터
PAM 신분증 확인 절차(로그인·sudo) 인증 프레임워크
모델 설명
DAC(임의) 소유자가 권한 결정 전통 UNIX 퍼미션
MAC(강제) 정책이 접근 강제. 소유자 권한 무관 SELinux, AppArmor

핵심: DAC가 허용해도 MAC(SELinux)이 거부하면 접근 불가. 둘 다 통과해야 동작한다. 이것이 "서비스 안 될 때 SELinux를 의심해야 하는 이유"다. 권한(chmod)만 보고 "다 됐는데"라고 착각하기 쉽기 때문이다.

SELinux — 모든 것에 레이블을 붙인다

NSA가 기여한 MAC 구현이다. 모든 프로세스와 파일에 레이블(컨텍스트)을 붙이고, 정책이 허용한 관계만 접근을 허용한다. "웹 서버 프로세스가 웹 콘텐츠 디렉토리만 읽게" 같은 세밀한 통제가 가능하다.

모드 동작 용도
enforcing 정책 위반 차단 + 로그 운영 기본(Rocky 10 기본)
permissive 위반 허용 + 로그만(감사/디버그) 문제 원인 파악
disabled 완전 비활성 권장 안 함(재부팅 필요, 보안 약화)

정책은 RHEL 10에서 targeted(기본, 주요 서비스만 confinement)이 표준이다.

SELinux 컨텍스트(레이블)

user:role:type:mls
예: unconfined_u:object_r:httpd_sys_content_t:s0
     │           │        │                  │
     │           │        │                  └ MLS 레벨
     │           │        └ 타입(가장 중요, 정책 매칭 단위)
     │           └ 롤
     └ 사용자

type(_t 접미사)가 정책 적용의 핵심이다. httpd_t 프로세스는 httpd_sys_content_t 파일만 읽도록 허용된다. 파일을 cp/mv/var/www/html에 넣으면 이 type 레이블이 잘못되고, SELinux가 차단한다 — 도입부의 403 사태.

firewalld — 네트워크 검문대

동적 방화벽 관리 데몬. RHEL 10 백엔드는 nftables(iptables는 unmaintained, 단계적 제거).

flowchart LR
    NIC[네트워크 인터페이스] --> ZONE[Zone<br/>trusted/public/dmz...]
    ZONE --> RULE[규칙: 서비스/포트/소스]
    RULE --> BACKEND[nftables 백엔드]
    BACKEND --> KERNEL[커널 패킷 필터]
  • Zone: 신뢰 수준별 규칙 묶음. 인터페이스를 zone에 할당. public(기본), trusted(모두 허용), block, dmz, home, work.
  • Service: 미리 정의된 포트/프로토콜 묶음(http, ssh, dns).
  • Runtime vs Permanent: 기본은 임시(재시작 시 사라짐). --permanent로 영구화한 뒤 --reload.

PAM과 sudo — 인증과 권한 위임

PAM(Pluggable Authentication Modules)

인증을 모듈화한 프레임워크. 로그인·sudo·ssh 등이 PAM을 통해 인증한다.

flowchart LR
    APP[응용: login, sshd, sudo] --> PAM[PAM 라이브러리]
    PAM --> CONF[/etc/pam.d/<service>/]
    CONF --> MOD[모듈: pam_unix, pam_sss, ...]
    MOD --> BACKEND[로컬/LDAP/Kerberos/IdM]
  • 설정: /etc/pam.d/<서비스>.
  • 관리 도구: authselect(RHEL 10). PAM/NSS 설정을 프로필 단위로 관리. 직접 /etc/pam.d 편집 금지(authselect가 덮어씀).
  • 백엔드 통합: SSSD → IdM/AD/LDAP.

sudo — 권한 위임

일반 사용자가 root 권한 명령을 제한적으로 실행한다.

  • 설정: /etc/sudoers/etc/sudoers.d/*. 반드시 visudo로 편집(문법 검증).
  • 일반 사용자를 wheel 그룹에 넣으면 sudo 사용 가능(RHEL 기본).

/etc/sudoers를 일반 에디터로 고치다 문법을 틀리면 모든 sudo가 막힌다 — root조차 못 된다. 그래서 visudo가 저장 시 문법을 검증한다.

시스템 전체 암호화 정책(Crypto Policies)

RHEL은 TLS/SSH/IPsec/DNSSEC/Kerberos 허용 알고리즘을 전역 정책으로 통일 관리한다: DEFAULT(운영 기본, 현대 알고리즘), LEGACY(구형 호환), FUTURE(더 강력), FIPS(FIPS 140 준수).

Rocky 10에서 직접 확인하기

미검증(출처 인용). Rocky 10 VM에서 실행 권장.

# SELinux — 서비스 안 될 때 가장 먼저
getenforce                       # 현재 모드
sestatus                         # 상세(모드, 정책)
sudo setenforce 0                # Permissive(임시)
sudo setenforce 1                # Enforcing

ls -Z /var/www/html/index.html   # 파일 컨텍스트
ps -eZ | grep httpd              # 프로세스 컨텍스트

# AVC 거부 로그(서비스 안 될 때)
sudo ausearch -m AVC -ts recent
sudo journalctl -t setroubleshoot
unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html

SELinux 문제 해결 흐름:

sudo ausearch -m AVC -ts recent | tail          # 1. 거부 로그에서 AVC 확인
sudo restorecon -Rv /var/www/html               # 2. 컨텍스트 복구(잘못된 레이블 교정)
# 비표준 경로/포트는 semanage로 레이블 추가
sudo semanage fcontext -a -t httpd_sys_content_t "/custom/web(/.*)?"
sudo restorecon -Rv /custom/web
# 반복 거부는 정책 모듈 자동 생성
sudo grep "...denied" /var/log/audit/audit.log | audit2allow -M mypol
sudo semodule -i mypol.pp
# firewalld
sudo systemctl status firewalld
firewall-cmd --get-default-zone
firewall-cmd --list-all                 # 현재 zone 전체

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload              # 영구 → 런타임 반영

sudo firewall-cmd --permanent --change-interface=enp0s3 --zone=public
public (active)
  services: cockpit dhcpv6-client ssh
  ports: 8080/tcp
# PAM / authselect
authselect current                    # 현재 프로필
cat /etc/pam.d/system-auth            # authselect 관리(직접 편집 금지)
sudo authselect select sssd --update  # 프로필 변경
# sudo
sudo -l                               # 내가 실행 가능한 명령
sudo visudo                           # 설정 편집(반드시 visudo)
echo "alice ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx" | sudo tee /etc/sudoers.d/alice
# 암호화 정책
update-crypto-policies --show         # DEFAULT
sudo update-crypto-policies --set FUTURE

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"설정 맞는데 안 되면 Apache 설정을 뒤진다" 9할 SELinux. permissive로 전환 후 AVC 로그 먼저
"SELinux 귀찮으니 disabled로" 금지. 보안 무력화 + 재부팅 시 레이블 재작성(매우 느림). 차라리 permissive
"firewall-cmd --add-service=http 영구다" 아니다. --permanent 없으면 재시작 시 사라짐
"/etc/pam.d/system-auth 직접 고친다" authselect가 다음 업데이트에 덮어씀. authselect로
"/etc/sudoers를 에디터로 고친다" 문법 틀리면 모든 sudo 막힘(root 못 됨). 반드시 visudo
"SELinux는 무작위로 차단" 아니다. 컨텍스트 매칭 안 되면. restorecon으로 교정
"firewalld 임시 규칙도 부팅 후 유지" 사라짐. 운영은 --permanent + --reload
"포트 열면 서비스 접속된다" SELinux가 비표준 포트 차단 가능. semanage port로 허용 추가 필요

더 깊이

  • SELinux boolean: 세밀한 토글 스위치. setsebool -P httpd_can_network_connect on(웹 서버가 네트워크 연결 허용). getsebool -a로 전체 확인.
  • auditd와 침입 탐지: auditd는 시스템 호출·파일 접근을 상세 로깅한다(/var/log/audit/audit.log). ausearch/aureport로 분석. 침해 사후 조사의 핵심.
  • IdM(Identity Management): RHEL의 중앙 인증(LDAP+Kerberos+DNS+CA 통합). Active Directory와 신뢰 관계 구성 가능.

요약 — 이 글의 결론

  • DAC(소유자 결정) + MAC(정책 강제, SELinux). 둘 다 통과해야 접근 — 이게 "권한 줬는데 왜 안 되지?"의 답.
  • SELinux: 레이블(컨텍스트 user:role:type:mls) 기반. type(_t)이 핵심. 모드는 enforcing(기본)/permissive/disabled(금지). "설정 맞는데 403"은 9할 컨텍스트 문제 → restorecon.
  • firewalld: zone(public/trusted/dmz) + service. 백엔드 nftables(Rocky 10). --permanent + --reload로 영구화.
  • PAM: /etc/pam.d/. authselect로 관리(직접 편집 금지).
  • sudo: visudo만. wheel 그룹이 기본 sudo 권한. 에디터로 고치면 문법 틀렸을 때 전체 마비.
  • Crypto policies: DEFAULT/LEGACY/FUTURE/FIPS.
  • 서비스 안 될 때 3단계: (1) 서비스 상태 (2) firewalld 포트 (3) SELinux 컨텍스트.

생각해 볼 문제

  1. DAC와 MAC의 차이. 둘 다 통과해야 하는 이유는?
  2. SELinux의 enforcing/permissive/disabled 차이. 왜 disabled를 피하는가?
  3. SELinux 컨텍스트에서 가장 중요한 필드는? 왜?
  4. "httpd 설정 다 맞는데 403"일 때 가장 먼저 확인할 것은?
  5. firewalld의 runtime과 permanent 차이. 운영에선 어떻게?
  6. /etc/pam.d/system-auth를 직접 고치면 안 되는 이유는?
  7. /etc/sudoers를 일반 에디터로 고치면 위험한 이유는? 안전한 도구는?
  8. Rocky 10의 firewalld 백엔드는?

참고

'Tech Artifacts > Linux' 카테고리의 다른 글

Linux - 11. services  (0) 2026.07.09
Linux - 09. network  (0) 2026.07.09
Linux - 08. package  (0) 2026.07.09
Linux - 07. shell script  (0) 2026.07.09
Linux - 06. io device  (0) 2026.07.09