3 분 소요

“포트를 8080으로 바꿨는데 서비스가 시작은 되고 접속이 안 된다”, “웹 서버가 /data 경로의 파일을 못 읽는다” 같은 증상의 상당수는 SELinux의 거부 로그를 확인하지 않아 생긴 일입니다. Rocky Linux 9는 enforcing이 기본이고 targeted 정책이 활성화돼 있으므로, 운영자는 문제가 생겼을 때 먼저 audit 로그를 읽을 줄 알아야 합니다.

1. 현재 상태 빠르게 파악

# 현재 모드
getenforce         # Enforcing / Permissive / Disabled

# 정책·모드 종합
sestatus

# 최근 거부 건수 요약
ausearch -m AVC,USER_AVC -ts recent --format text | head

Enforcing에서 거부가 자꾸 발생한다면, 정책을 건드리기 전에 로그를 먼저 읽는 것이 순서입니다.

2. audit 로그 해석하는 최소 루틴

2-1. 무엇이 거부됐는지 찾기

# 최근 10분치 AVC 거부
ausearch -m AVC -ts recent

# 특정 서비스 관련
ausearch -m AVC -c nginx

# 타임스탬프로 범위 지정
ausearch -m AVC -ts today -te now

일반적인 출력 형식은 다음과 같습니다.

type=AVC msg=audit(1728720012.345:678): avc: denied { write } for
  pid=1234 comm="nginx" name="uploads" dev="dm-0" ino=98765
  scontext=system_u:system_r:httpd_t:s0
  tcontext=system_u:object_r:var_t:s0
  tclass=dir permissive=0

읽을 때 주목해야 할 세 가지:

  • denied { ... } — 어떤 권한이 거부됐는지
  • scontext — 누가(예: httpd_t)
  • tcontext, tclass — 무엇에 대해(예: var_t 타입의 디렉터리)

2-2. 사람이 읽는 요약과 권장 해결책

# 간단 요약
sealert -a /var/log/audit/audit.log | head -80

# 특정 거부에 대한 추천 정책 생성
ausearch -m AVC -ts recent | audit2allow -a -M nginx_uploads
ls nginx_uploads.pp nginx_uploads.te

audit2allow의 출력은 참고용이지 그대로 적용하는 것은 위험합니다. 꼭 .te 파일을 열어 허용되는 범위를 확인한 뒤 로드합니다.

3. 자주 마주치는 시나리오

3-1. 포트 변경

# 웹 서버를 8080 대신 8181로 서비스
semanage port -l | grep -E 'http_port_t|8181'
semanage port -a -t http_port_t -p tcp 8181

-a 대신 이미 다른 타입에 할당된 포트라면 -m(modify)를 써야 합니다.

3-2. 커스텀 경로의 파일 컨텍스트

데이터 디렉터리를 /var/www 외 위치에 두는 경우:

# 영구 규칙 등록
semanage fcontext -a -t httpd_sys_content_t '/srv/app(/.*)?'

# 기존 파일에 적용
restorecon -Rv /srv/app

# 확인
ls -lZ /srv/app | head

-a 없이 -l | grep /srv/app으로 현재 등록 상태를 먼저 살펴봅니다. /data/logs처럼 쓰기가 필요한 경로라면 httpd_sys_rw_content_t를 씁니다.

3-3. 부울(boolean) 토글

SELinux 정책에는 상황별 on/off 스위치가 많습니다.

# 설명과 함께 모두 보기
getsebool -a | grep -i httpd | head

# 외부 네트워크로 나가는 HTTP 요청 허용
setsebool -P httpd_can_network_connect on

# DB 커넥션 허용
setsebool -P httpd_can_network_connect_db on

-P는 재부팅 후에도 유지됩니다.

3-4. 일시적 허용 모드 — 단, 끄지 말고 전환

# 특정 도메인만 일시적으로 permissive로 전환
semanage permissive -a httpd_t

# 되돌리기
semanage permissive -d httpd_t

전체를 setenforce 0으로 끄는 것보다, 문제 도메인만 좁혀서 거부 로그를 수집하고 정책을 만든 뒤 다시 enforcing으로 돌리는 흐름이 안전합니다.

3-5. 커스텀 정책 모듈

# 문제 상황 재현 → 로그 수집 → 정책 생성
setenforce 0
systemctl restart myapp
# ... 여기서 문제 재현 ...
setenforce 1

ausearch -m AVC -c myapp | audit2allow -M myapp_local
semodule -i myapp_local.pp

# 로드된 모듈 확인
semodule -l | grep myapp_local

# 롤백
semodule -r myapp_local

audit2allow가 생성한 .te 파일은 반드시 열어 수정합니다. 예를 들어 allow myapp_t self:capability sys_admin; 같은 광범위한 권한이 들어가 있다면, 실제 필요한 것만 남기고 나머지는 지워 다시 컴파일합니다.

checkmodule -M -m -o myapp_local.mod myapp_local.te
semodule_package -o myapp_local.pp -m myapp_local.mod
semodule -i myapp_local.pp

4. 컨테이너 환경

Podman·Docker가 설치된 Rocky 9에서는 컨테이너 내 프로세스가 container_t로 라벨됩니다. 호스트 디렉터리를 바인드 마운트할 때는 접미사 :z(공유) 또는 :Z(전용)를 붙여 자동 리라벨을 활용합니다.

# 자동 리라벨로 마운트
podman run -d \
  -v /srv/app/data:/data:Z \
  -p 8080:8080 \
  myapp:latest

:Z는 호스트 디렉터리의 컨텍스트를 해당 컨테이너 전용 라벨로 바꾸므로, 여러 컨테이너가 같은 디렉터리를 공유한다면 :z를 써야 합니다.

5. 빠른 진단 알리아스

매번 긴 명령을 치기 번거로우면 다음 함수를 ~/.bashrc에 두면 편합니다.

# 최근 5분간의 SELinux 거부 요약
selrecent() {
    ausearch -m AVC,USER_AVC -ts recent 2>/dev/null \
      | audit2allow -a \
      | head -40
}

# 특정 서비스의 거부만
selfor() {
    ausearch -m AVC -c "$1" -ts today 2>/dev/null | audit2allow -a
}

6. 영구 비활성화는 최후의 선택

/etc/selinux/config에서 SELINUX=disabled로 두면 보호 기능이 완전히 사라집니다. 더불어 Rocky 9에서는 disabled로 부팅 시 파일 컨텍스트가 갱신되지 않아, 나중에 다시 활성화하려 할 때 전체 재라벨링(fixfiles -F onboot 후 재부팅)이 필요합니다. 트러블슈팅 시간 대신 비활성화를 택하는 흐름은 장기적으로 더 많은 비용을 불러옵니다.


SELinux의 거부 메시지는 얼핏 불친절해 보이지만, ausearch + audit2allow + sealert 세 도구를 익히면 대부분의 문제는 10분 안에 추적됩니다. 운영 중인 Rocky 9 서버가 있다면, 장애가 나기 전에 한 번씩 sealert -a를 돌려 누적된 경고를 점검해 두는 습관이 든든합니다.

댓글남기기