Red Hat Certified System Administrator (RHCSA) #13 SELinux 깊이: contexts, booleans, troubleshooting (audit2allow)

12 분 소요

#12 firewalld와 SSH 키 인증에서 포트를 열고 키로 로그인하는 작업을 다뤘습니다. 그런데 방화벽을 열고 서비스를 띄웠는데도 접속이 막힌다면, 다음으로 의심할 대상은 거의 항상 SELinux입니다. SELinux는 RHCSA 응시자가 가장 많이 발목 잡히는 영역이자, 동시에 패턴만 잡으면 가장 확실하게 점수를 챙기는 영역이기도 합니다. 이번 글에서는 컨텍스트,boolean,포트 라벨,트러블슈팅을 실기 관점에서 끝까지 정리하겠습니다.

SELinux의 핵심은 단순합니다. 모든 프로세스와 파일에 라벨(context)을 붙이고, 정책이 허용한 조합만 통과시킨다는 것입니다. 표준 리눅스 퍼미션(rwx)을 모두 통과한 작업이라도, SELinux 정책이 거부하면 그 작업은 실패합니다. RHCSA에서 마주치는 거의 모든 SELinux 문제는 “퍼미션은 맞는데 동작하지 않는” 형태로 나타납니다.

SELinux 모드: enforcing, permissive, disabled #

SELinux에는 세 가지 모드가 있습니다.

모드동작
enforcing정책을 강제. 거부 대상은 차단되고 audit 로그에 기록
permissive정책을 강제하지 않음. 차단은 안 하되 거부 사항을 로그에만 기록
disabledSELinux 비활성화. 라벨링도 멈춤

permissive는 “막지는 않지만 무엇이 막혔을지 로그로 보여 주는” 모드라서, 트러블슈팅 단계에서 매우 유용합니다.

현재 모드 확인과 임시 전환 #

현재 모드는 getenforce로 봅니다. sestatus는 더 자세한 상태를 보여 줍니다.

getenforce
# Enforcing

sestatus
# SELinux status:                 enabled
# Current mode:                   enforcing
# Mode from config file:          enforcing
# Policy from config file:        targeted

setenforce로 enforcing과 permissive를 즉시 전환할 수 있습니다. 다만 이 전환은 재부팅하면 사라지는 임시 설정입니다.

setenforce 0   # permissive로 전환
setenforce 1   # enforcing으로 전환
getenforce
# Permissive

setenforce로는 disabled로 갈 수 없습니다. enforcing과 permissive 사이만 오갑니다.

영구 모드: /etc/selinux/config #

재부팅 후에도 유지되는 모드는 /etc/selinux/configSELINUX= 값으로 정합니다. RHCSA에서 “SELinux를 enforcing으로 영구 설정하라” 같은 문제가 나오면 이 파일을 고쳐야 합니다.

cat /etc/selinux/config
# SELINUX=enforcing
# SELINUXTYPE=targeted

SELINUX= 값을 enforcing, permissive, disabled 중 하나로 바꾸면 다음 부팅부터 적용됩니다. 시험에서는 보통 enforcing을 영구로 요구하므로, setenforce 1로 즉시 적용하고 config 파일에도 enforcing을 써 두는 것이 안전합니다.

setenforce 1
sed -i 's/^SELINUX=.*/SELINUX=enforcing/' /etc/selinux/config
grep '^SELINUX=' /etc/selinux/config
# SELINUX=enforcing

disabled에서 enforcing으로 바꿀 때는 파일 시스템 전체의 라벨이 빠져 있을 수 있어, 다음 부팅에서 자동 relabel이 일어납니다. relabel을 강제하려면 touch /.autorelabel 후 재부팅합니다.

컨텍스트(context): 라벨의 구조 #

SELinux는 모든 객체에 컨텍스트를 붙입니다. 컨텍스트는 콜론으로 구분된 네 부분으로 이뤄집니다.

user:role:type:level
system_u:object_r:httpd_sys_content_t:s0

RHCSA에서 실제로 다루는 부분은 거의 type(세 번째 필드) 하나입니다. 정책 판단의 대부분이 type 사이의 허용 규칙(type enforcement)으로 이뤄지기 때문입니다. 예를 들어 웹 서버 프로세스의 type은 httpd_t이고, 웹 콘텐츠 파일의 type은 httpd_sys_content_t입니다. 정책이 “httpd_thttpd_sys_content_t를 읽을 수 있다"고 허용하므로 웹 서버가 그 파일을 서비스합니다.

파일 컨텍스트 보기: ls -Z #

-Z 옵션은 SELinux 컨텍스트를 보여 줍니다. ls -Z로 파일의 컨텍스트를 확인합니다.

ls -Z /var/www/html/
# unconfined_u:object_r:httpd_sys_content_t:s0 index.html

프로세스 컨텍스트 보기: ps -Z #

ps -Z로 실행 중인 프로세스의 컨텍스트를 봅니다.

ps -eZ | grep httpd
# system_u:system_r:httpd_t:s0 1234 ? 00:00:00 httpd

id -Z는 현재 셸의 컨텍스트를, ss -Znetstat -Z는 소켓의 컨텍스트를 보여 줍니다.

컨텍스트가 틀리면 생기는 일 #

RHCSA에서 가장 흔한 시나리오입니다. 웹 콘텐츠를 표준 위치가 아닌 곳에 두거나, 다른 곳에서 파일을 옮겨 오면 컨텍스트가 어긋납니다. 예를 들어 홈 디렉터리에서 만든 파일을 /var/www/htmlmv하면, 그 파일은 원래 위치의 컨텍스트(user_home_t 등)를 그대로 들고 옵니다. 그 결과 웹 서버 프로세스(httpd_t)가 읽지 못해 403이 납니다.

여기서 핵심 규칙이 있습니다.

  • cp로 복사하면 대상 디렉터리의 기본 컨텍스트를 새로 받습니다.
  • mv로 이동하면 원본의 컨텍스트를 그대로 유지합니다.

그래서 파일을 옮긴 뒤에는 컨텍스트를 바로잡아야 합니다.

컨텍스트 고치기: chcon vs restorecon vs semanage #

컨텍스트를 바꾸는 방법은 세 가지이며, 무엇을 쓰느냐가 RHCSA 점수를 가릅니다.

chcon: 임시 변경(시험에서는 피한다) #

chcon은 파일의 컨텍스트를 직접 바꿉니다. 즉시 적용되지만, 정책 데이터베이스에는 반영되지 않습니다. 따라서 나중에 restorecon이 돌거나 파일 시스템 relabel이 일어나면 원래대로 돌아갑니다.

chcon -t httpd_sys_content_t /var/www/html/index.html
chcon -R -t httpd_sys_content_t /web/content   # 재귀 변경

chcon은 빠르게 테스트할 때만 쓰고, 시험 답안으로는 권장하지 않습니다. 채점 전 시스템이 relabel되면 답이 풀려 버릴 수 있기 때문입니다.

semanage fcontext + restorecon: 영구 변경(정답) #

영구적으로 컨텍스트를 고치는 올바른 방법은 두 단계입니다.

  1. semanage fcontext -a -t정책에 기본 컨텍스트 규칙을 추가한다.
  2. restorecon으로 그 규칙을 실제 파일에 적용한다.

예를 들어 /web 아래를 웹 콘텐츠로 쓰려면 다음과 같이 합니다.

# 1) 정책에 규칙 추가: /web이하 전부를 httpd_sys_content_t로 정의
semanage fcontext -a -t httpd_sys_content_t "/web(/.*)?"

# 2) 규칙을 실제 파일에 적용
restorecon -R -v /web

"/web(/.*)?"는 정규식으로, /web 디렉터리 자체와 그 아래 모든 경로를 의미합니다. 이렇게 하면 정책 자체에 규칙이 남으므로, relabel이 일어나도 컨텍스트가 유지됩니다. 이것이 RHCSA에서 요구하는 영구 적용입니다.

semanagepolicycoreutils-python-utils 패키지에 들어 있습니다. 없으면 설치합니다.

dnf install -y policycoreutils-python-utils

규칙을 확인하거나 삭제할 때는 다음과 같이 합니다.

semanage fcontext -l | grep '/web'   # 추가한 규칙 확인
semanage fcontext -d "/web(/.*)?"    # 규칙 삭제

restorecon: 기본 컨텍스트로 되돌리기 #

restorecon은 정책에 정의된 기본 컨텍스트를 파일에 다시 적용합니다. mv로 옮겨 와 컨텍스트가 어긋난 표준 위치의 파일은, semanage 없이 restorecon만으로 바로잡힙니다. /var/www/html은 이미 정책에 httpd_sys_content_t로 정의되어 있기 때문입니다.

restorecon -R -v /var/www/html
# Relabeled /var/www/html/index.html
#   from unconfined_u:object_r:user_home_t:s0
#   to   unconfined_u:object_r:httpd_sys_content_t:s0

정리하면, 표준 위치라면 restorecon만, 비표준 위치라면 semanage fcontext로 규칙을 추가한 뒤 restorecon입니다.

boolean: 정책 동작을 켜고 끄기 #

컨텍스트가 다 맞아도 막히는 경우가 있습니다. SELinux 정책에는 boolean이라는 on/off 스위치가 있어, 특정 동작을 정책 차원에서 허용하거나 차단합니다. 예를 들어 웹 서버가 사용자 홈 디렉터리를 서비스하거나, 외부로 네트워크 연결을 맺는 동작은 기본적으로 boolean으로 꺼져 있습니다.

boolean 목록 보기: getsebool #

전체 boolean은 getsebool -a로 봅니다. semanage boolean -l은 기본값과 설명까지 함께 보여 줍니다.

getsebool -a | grep httpd
# httpd_can_network_connect --> off
# httpd_enable_homedirs --> off
# httpd_use_nfs --> off

semanage boolean -l | grep httpd_can_network_connect
# httpd_can_network_connect (off , off)  Allow httpd to ...

boolean 설정: setsebool -P #

setsebool로 boolean을 켜고 끕니다. 여기서 -P 옵션이 핵심입니다. -P 없이 켜면 메모리에만 적용되어 재부팅하면 꺼집니다. -P를 붙여야 정책에 영구 저장됩니다.

setsebool httpd_can_network_connect on      # 임시. 재부팅하면 꺼짐
setsebool -P httpd_can_network_connect on   # 영구. 정답

RHCSA에서 “데이터베이스에 연결하는 웹 앱을 동작시켜라” 같은 문제는 십중팔구 httpd_can_network_connect-P로 켜는 것이 답입니다. -P를 빼면 채점 시점에 재부팅되어 실점합니다.

포트 레이블: semanage port #

SELinux는 포트에도 라벨을 붙입니다. 서비스를 표준이 아닌 포트로 돌리면, 그 포트의 라벨이 서비스 type과 맞지 않아 바인딩이 거부됩니다. 예를 들어 sshd를 22번이 아닌 2222번으로 바꾸면, 2222번에는 ssh_port_t 라벨이 없어 sshd가 그 포트를 열지 못합니다. firewalld로 포트를 열어도 소용없습니다. SELinux 단에서 막히기 때문입니다.

현재 포트 라벨 보기 #

semanage port -l | grep ssh
# ssh_port_t   tcp   22

semanage port -l | grep http_port_t
# http_port_t  tcp   80, 81, 443, 488, 8008, 8009, 8443, 9000

포트 라벨 추가: semanage port -a -t #

비표준 포트를 쓰려면 해당 포트에 알맞은 type을 부여합니다.

# sshd를 2222 번으로 쓰려면 그 포트에 ssh_port_t 부여
semanage port -a -t ssh_port_t -p tcp 2222
semanage port -l | grep ssh
# ssh_port_t   tcp   2222, 22

이미 다른 type이 점유한 포트라면 -a(추가)가 아니라 -m(수정)을 씁니다. 추가할 때 already defined 오류가 나면 -m으로 바꾸는 것이 정석입니다.

semanage port -m -t http_port_t -p tcp 8888

비표준 포트로 서비스를 옮기는 RHCSA 문제는 firewalld 포트 열기 + SELinux 포트 라벨 추가를 한 묶음으로 처리해야 완성됩니다. 둘 중 하나만 해도 접속이 안 됩니다.

트러블슈팅: 거부 로그를 추적하고 정책 만들기 #

SELinux 거부는 모두 AVC(Access Vector Cache) denial로 audit 로그에 기록됩니다. 무엇이 왜 막혔는지를 이 로그에서 읽어 내는 것이 트러블슈팅의 출발점입니다. 로그는 /var/log/audit/audit.log에 있습니다.

ausearch: audit 로그에서 거부 찾기 #

ausearch로 최근 AVC 거부를 모아 봅니다.

ausearch -m AVC -ts recent
# type=AVC msg=audit(...): avc:  denied  { read } for
#   pid=1234 comm="httpd" name="index.html"
#   scontext=system_u:system_r:httpd_t:s0
#   tcontext=unconfined_u:object_r:user_home_t:s0  tclass=file

여기서 읽어야 할 부분은 명확합니다. comm="httpd"(누가), denied { read }(무엇을), tcontext=...user_home_t(어떤 라벨의 대상에)입니다. httpd_t 프로세스가 user_home_t 파일을 읽으려다 막혔다는 뜻이니, 컨텍스트가 잘못된 전형적인 사례입니다. 답은 restorecon이나 semanage fcontext로 라벨을 바로잡는 것입니다.

sealert: setroubleshoot의 권고 읽기 #

setroubleshoot-server 패키지를 설치하면, 거부가 생길 때마다 /var/log/messages에 사람이 읽기 쉬운 분석과 권고 명령이 기록됩니다. sealert로 자세히 봅니다.

dnf install -y setroubleshoot-server
sealert -a /var/log/audit/audit.log

sealert는 종종 “이 문제는 다음 명령으로 해결하라"며 setsebool -P ...restorecon ... 같은 구체적인 명령을 제시합니다. 다만 권고를 그대로 따르기 전에, 그 권고가 정말 의도한 동작인지 판단해야 합니다.

audit2allow: 거부 로그로 정책 모듈 만들기 #

표준 boolean이나 컨텍스트 수정으로 풀리지 않는, 커스텀 정책이 필요한 거부에는 audit2allow를 씁니다. 거부 로그를 입력으로 받아, 그 동작을 허용하는 정책 규칙을 생성합니다.

먼저 무엇을 허용하게 될지 규칙을 눈으로 확인합니다.

ausearch -m AVC -ts recent | audit2allow
# allow httpd_t user_home_t:file { read };

규칙이 타당하다고 판단되면, 모듈로 만들어 적재합니다.

# myhttpd 라는 이름의 정책 모듈 생성
ausearch -m AVC -ts recent | audit2allow -M myhttpd

# 생성된 모듈 적재
semodule -i myhttpd.pp

audit2allow -M.te(정책 소스)와 컴파일된 .pp(정책 패키지) 파일을 만들고, semodule -i로 그 .pp를 시스템 정책에 추가합니다. 적재된 커스텀 모듈은 semodule -l로 확인하고, semodule -r myhttpd로 제거합니다.

트러블슈팅 우선순위 #

audit2allow는 강력하지만 마지막 수단입니다. 거부를 무조건 허용하는 모듈을 만드는 것은, 진짜 원인(잘못된 컨텍스트나 꺼진 boolean)을 덮어 버릴 수 있기 때문입니다. RHCSA에서의 권장 순서는 다음과 같습니다.

  1. 컨텍스트 확인. ls -Z로 라벨을 보고, 어긋났으면 restorecon 또는 semanage fcontext로 바로잡는다.
  2. boolean 확인. 동작 자체가 boolean으로 꺼져 있는지 보고, 맞으면 setsebool -P로 켠다.
  3. 포트 라벨 확인. 비표준 포트면 semanage port로 라벨을 부여한다.
  4. 위로 풀리지 않는 진짜 커스텀 동작에만 audit2allow로 모듈을 만든다.

permissive로 잠깐 바꿔 보는 것도 진단에 유용합니다. permissive에서 서비스가 정상 동작하면 원인은 분명히 SELinux이고, 그동안 쌓인 거부 로그를 한 번에 모아 분석할 수 있습니다. 진단이 끝나면 반드시 enforcing으로 되돌립니다.

시험 포인트 #

  • chcon vs restorecon. chcon은 임시이고 relabel하면 풀린다. 영구 답안은 표준 위치면 restorecon, 비표준 위치면 semanage fcontext -a -trestorecon이다. 시험 답안으로 chcon은 쓰지 않는다.
  • setsebool의 -P. -P를 빼면 재부팅 시 사라져 채점에서 실점한다. boolean을 켤 때는 항상 setsebool -P다.
  • semanage fcontext 정규식. 디렉터리 이하 전체는 "/path(/.*)?"로 쓴다. 따옴표로 감싸 셸이 별표를 해석하지 못하게 한다.
  • 비표준 포트는 두 곳. firewalld로 포트를 열고, semanage port -a -t로 SELinux 라벨도 부여해야 접속이 완성된다.
  • 영구 모드는 /etc/selinux/config. setenforce는 임시다. 영구 enforcing은 config 파일의 SELINUX=enforcing까지 확인한다.
  • 트러블슈팅 도구. 로그는 /var/log/audit/audit.log, 거부 검색은 ausearch -m AVC, 권고는 sealert, 정책 생성은 audit2allow -Msemodule -i다.
  • semanage가 없으면 policycoreutils-python-utils를, sealert가 없으면 setroubleshoot-server를 설치한다.

정리 #

이번 글에서 잡은 것:

  • SELinux 모드. getenforce/setenforce로 임시 전환, /etc/selinux/config로 영구 설정. enforcing,permissive,disabled
  • 컨텍스트. ls -Z/ps -Z로 확인. 핵심은 type 필드. mv는 라벨을 들고 오고 cp는 대상 기본값을 받는다
  • 컨텍스트 고치기. chcon은 임시, 영구는 semanage fcontext -a -t + restorecon. 표준 위치는 restorecon만으로 충분
  • boolean. getsebool -a로 확인, setsebool -P로 영구 설정. 동작 단위 on/off 스위치
  • 포트 라벨. semanage port -a -t로 비표준 포트에 type 부여. firewalld와 한 묶음
  • 트러블슈팅. audit.logausearch/sealertaudit2allow로 정책 모듈. 단, 컨텍스트,boolean,포트를 먼저 점검

SELinux는 RHEL 실무 트랙에서 만져 본 개념을 시험 관점에서 다시 조이는 영역입니다. 거부가 보이면 당황하지 말고, 컨텍스트,boolean,포트의 순서로 점검하는 습관을 들이면 RHCSA에서 SELinux는 오히려 확실한 득점원이 됩니다.

다음: 컨테이너 관리 #

보안 영역까지 마무리했습니다. 이제 RHCSA 출제 범위의 마지막 큰 축인 컨테이너로 넘어갑니다.

#14 컨테이너 관리: Podman, systemd integration (quadlet)에서는 루트 없이 컨테이너를 돌리는 Podman의 기본 명령부터, 이미지를 받아 실행하고 스토리지를 마운트하는 법, 그리고 컨테이너를 systemd 서비스로 등록해 부팅 시 자동 시작하게 만드는 quadlet 통합까지 직접 쳐 보며 정리하겠습니다.

X