RHEL 고급 #4 SELinux 고급 — 정책 직접 작성과 audit2allow
중급 #1 SELinux 입문에서 모드, 라벨, AVC 거부, audit2allow로 풀어가는 흐름까지 봤습니다. 이번 글은 그 위 한 층입니다. audit2allow가 만들어주는 결과물의 정체가 무엇인지, 그게 부족할 때 직접 .te 파일을 손으로 작성해서 컴파일하는 절차, 정책 모듈을 시스템에 영구 등록해서 다음 부팅 후에도 살아남게 만드는 흐름까지 한 사이클로 다루겠습니다. 거부가 보일 때마다 setenforce 0으로 도망가지 않고 끝까지 따라가서 모듈로 굳히는 게 목표입니다.
RHEL 고급 시리즈에서 이번 글의 위치:
- #1 부팅 프로세스 — GRUB2, dracut, 복구 모드
- #2 커널 튜닝 — sysctl, tuned, kdump
- #3 성능 분석 — sar, top/htop, iostat, vmstat, perf
- #4 SELinux 고급 — 정책 직접 작성과 audit2allow ← 이번 글
- #5 보안 강화 — auditd, OpenSCAP, FIPS
- #6 Subscription / Satellite / Insights
- #7 Cockpit으로 GUI 관리와 web console
정책 파일의 정체 — .te / .fc / .if #
SELinux 정책 모듈은 다음 세 종류의 소스 파일로 구성됩니다.
| 파일 | 무엇을 담나 |
|---|---|
*.te | Type Enforcement — allow 룰, type 선언 |
*.fc | File Contexts — 어떤 경로에 어떤 라벨을 붙일지 |
*.if | Interface — 다른 모듈에서 호출할 수 있는 매크로 (선택) |
.te가 핵심입니다. .fc는 restorecon이 참고할 라벨 매핑이고, .if는 모듈을 라이브러리처럼 재사용할 수 있게 하는 인터페이스입니다. 작은 사용자 모듈은 보통 .te 하나로 끝납니다.
컴파일 파이프라인 #
mymodule.te ──checkmodule──▶ mymodule.mod ──semodule_package──▶ mymodule.pp
│
▼
semodule -i
(시스템 등록).pp (policy package)가 실제 시스템에 설치되는 단위. audit2allow -M도 결국 이 파이프라인을 한 명령으로 묶어 돌려줍니다.
audit2allow — 거부를 모듈로 #
중급 #1에서 본 명령을 다시 봅니다. AVC 거부가 떴을 때 그 거부를 허용하는 정책을 자동 생성합니다.
# 1. 무엇이 거부됐는지 확인
$ sudo ausearch -m AVC -ts recent
# 2. 거부 묶음을 모듈로 변환
$ sudo ausearch -m AVC -ts recent | audit2allow -M mywebapp
# 결과물:
# mywebapp.te — 사람이 읽을 수 있는 정책 소스
# mywebapp.pp — 컴파일된 정책 패키지
# 3. 시스템에 등록
$ sudo semodule -i mywebapp.pp생성된 .te를 한 번 들여다보는 게 핵심입니다. audit2allow는 거부된 동작을 그대로 허용하는 룰만 만들어주기 때문에, 공격자가 만들어낸 의도치 않은 거부를 그대로 허용해버릴 수도 있습니다. 모듈 등록 전에 반드시 사람이 검토해야 합니다.
생성된 .te의 구조 #
module mywebapp 1.0;
require {
type httpd_t;
type postgresql_port_t;
class tcp_socket name_connect;
}
#============= httpd_t ==============
allow httpd_t postgresql_port_t:tcp_socket name_connect;| 구역 | 의미 |
|---|---|
module mywebapp 1.0; | 모듈 이름과 버전 |
require { ... } | 이 모듈이 참조하는 type, class, attribute 선언 |
allow ... | 실제 허용 룰 |
읽는 법: httpd_t 타입의 프로세스가 postgresql_port_t 타입의 TCP 소켓에 name_connect (포트 연결) 하는 것을 허용합니다.
audit2allow의 한계 #
- type/attribute 신규 선언 불가 — 이미 시스템에 있는 type만 참조 가능. 새 type을 만들려면 직접
.te작성. - 거부된 룰만 — 거부가 한 번도 일어나지 않은 룰은 못 만듭니다. 한번 운영 환경에서 끝까지 돌려본 뒤에 모듈 생성하는 게 정석.
- 위험한 룰을 그대로 —
dac_override,setuid같은 capability 거부도 그냥 허용해버립니다. 사람 검토 필수.
직접 .te 작성과 컴파일 #
audit2allow 결과로는 부족할 때, 또는 새 type을 도입하고 싶을 때는 직접 작성합니다.
첫 모듈 — sample app 정책 #
가상의 시나리오. 우리 회사의 데몬 myappd가 /var/lib/myapp/에 데이터를 쓰고 /var/log/myapp.log에 로그를 남깁니다. 표준 방식으로 SELinux 정책을 부여해 보겠습니다.
policy_module(myapp, 1.0)
########################################
#
# 선언
#
type myapp_t;
type myapp_exec_t;
init_daemon_domain(myapp_t, myapp_exec_t)
type myapp_data_t;
files_type(myapp_data_t)
type myapp_log_t;
logging_log_file(myapp_log_t)
########################################
#
# myapp_t 룰
#
allow myapp_t myapp_data_t:dir { read write add_name remove_name search };
allow myapp_t myapp_data_t:file { create read write append unlink open getattr setattr };
allow myapp_t myapp_log_t:file { create append read open getattr setattr };
logging_log_filetrans(myapp_t, myapp_log_t, file)policy_module(myapp, 1.0)으로 시작하는 형식은 reference policy 매크로 방식. RHEL의 표준입니다. 위에서:
| 선언 | 의미 |
|---|---|
type myapp_t | 데몬 프로세스의 도메인 type |
type myapp_exec_t | 실행 파일의 type |
init_daemon_domain(myapp_t, myapp_exec_t) | systemd가 myapp_exec_t 실행 파일을 띄우면 myapp_t 도메인으로 전이 |
type myapp_data_t | 데이터 파일 type |
type myapp_log_t | 로그 파일 type |
files_type(...) | 일반 파일 시스템에 두는 type으로 등록 |
logging_log_file(...) | 로그 파일 type으로 등록 |
파일 컨텍스트 정의 #
/usr/sbin/myappd -- gen_context(system_u:object_r:myapp_exec_t,s0)
/var/lib/myapp(/.*)? gen_context(system_u:object_r:myapp_data_t,s0)
/var/log/myapp\.log.* -- gen_context(system_u:object_r:myapp_log_t,s0)(/.*)?는 디렉터리 자신과 그 아래 모든 경로. --는 일반 파일만, -d는 디렉터리만, 생략하면 모두.
컴파일과 설치 #
reference policy 매크로(policy_module, gen_context 등)를 쓰려면 reference policy 컴파일 환경이 필요합니다.
$ sudo dnf install -y selinux-policy-devel$ ls /usr/share/selinux/devel/Makefile
/usr/share/selinux/devel/Makefile
$ make -f /usr/share/selinux/devel/Makefile myapp.pp
Compiling targeted myapp module
...
Creating targeted myapp.pp policy package
rm tmp/myapp.mod tmp/myapp.mod.fc$ sudo semodule -i myapp.pp
# 설치 확인
$ sudo semodule -l | grep myapp
myapp
# 라벨 적용
$ sudo restorecon -Rv /usr/sbin/myappd /var/lib/myapp /var/log/myapp.log이걸로 myappd 데몬이 myapp_t 도메인에서 돌고, 자기 데이터 디렉터리와 로그 파일에만 접근하는 정책이 시스템에 영구적으로 등록됩니다.
모듈 제거 #
$ sudo semodule -r myappsemodule -r은 myapp.pp가 아니라 모듈 이름 을 받습니다. .pp 확장자 빼고.
type, attribute, allow의 문법 #
type과 attribute #
type은 객체에 붙이는 라벨 자체이고, attribute는 type 들의 묶음입니다.
attribute web_server_domain;
typeattribute httpd_t web_server_domain;
typeattribute nginx_t web_server_domain;
# attribute 단위로 룰 작성 — httpd_t와 nginx_t 둘 다에 적용
allow web_server_domain http_port_t:tcp_socket { name_bind name_connect };web_server_domain attribute에 새 웹 서버 type을 추가하기만 하면, 위 allow 룰이 자동으로 그 type에도 적용됩니다. 정책을 깔끔하게 유지하는 핵심 기법.
allow 룰의 형태 #
allow <소스 type> <대상 type>:<class> { <permission ...> };- 소스 type — 행위를 하는 주체 (보통 프로세스 도메인)
- 대상 type — 행위 대상 (파일, 소켓, 디렉터리 등)
- class — 객체의 종류 (file, dir, tcp_socket, …)
- permission — 무엇을 허용할지 (read, write, execute, name_bind, …)
# myapp_t가 자기 데이터 파일을 읽고 쓰기
allow myapp_t myapp_data_t:file { read write open getattr };
# myapp_t가 자기 데이터 디렉터리에 파일 생성
allow myapp_t myapp_data_t:dir { add_name write search };
# myapp_t가 8080 포트에 바인드
allow myapp_t http_port_t:tcp_socket name_bind;class 별로 가능한 permission 목록은 /etc/selinux/targeted/contexts/files/file_contexts와 seinfo 명령으로 확인.
$ sudo dnf install -y setools-console
$ seinfo --class file -x # file class의 모든 permission
$ seinfo -t myapp_t # type 정보
$ sesearch -A -s httpd_t # httpd_t가 소스인 모든 allow 룰부울 깊이 #
중급 #1에서 가볍게 본 부울을 다시 봅니다. 정책 안에 if 분기를 박아둔 스위치 입니다. 정책 자체를 다시 빌드하지 않고 정책의 동작을 바꿀 수 있습니다.
$ getsebool -a | head
abrt_anon_write --> off
abrt_handle_event --> off
abrt_upload_watch_anon_write --> on
...
# 특정 부울
$ getsebool httpd_can_network_connect
httpd_can_network_connect --> off
# 변경 (영구)
$ sudo setsebool -P httpd_can_network_connect on
# 변경 (일시)
$ sudo setsebool httpd_can_network_connect on-P가 영구 적용. 안 주면 재부팅 시 사라집니다.
자기 모듈에 부울 박기 #
gen_tunable(myapp_can_network_connect, false)
tunable_policy(`myapp_can_network_connect',`
corenet_tcp_connect_all_ports(myapp_t)
')위 정책을 컴파일,설치하면 myapp_can_network_connect 라는 부울이 시스템에 등록됩니다.
$ getsebool myapp_can_network_connect
myapp_can_network_connect --> off
$ sudo setsebool -P myapp_can_network_connect on운영자가 정책 컴파일을 다시 안 해도 한 줄로 정책의 동작을 바꿀 수 있게 만드는 표준 기법.
디버깅 흐름 #
거부가 떴을 때 끝까지 따라가는 한 사이클을 정리.
1. 거부 확인 #
# audit 로그에서 직접
$ sudo ausearch -m AVC -ts recent
# 더 친절한 해설 (setroubleshoot)
$ sudo dnf install -y setroubleshoot-server
$ sudo journalctl -t setroubleshoot --since "10 min ago"
# sealert로 분석
$ sudo sealert -a /var/log/audit/audit.logsetroubleshoot가 가장 도움이 됩니다. 거부가 일어난 원인 후보와 보통의 해결책을 자연어로 설명합니다.
2. dontaudit 룰 때문에 안 보일 때 #
기본 정책에는 시끄러운 거부를 숨기는 dontaudit 룰이 많습니다. 디버깅 중엔 임시로 끄세요.
$ sudo semodule -DB
# 디버깅 끝나면 다시 활성화
$ sudo semodule -B3. Permissive도메인으로 격리 #
전체 시스템을 Permissive로 떨어뜨리지 말고, 의심 도메인만 Permissive로:
$ sudo semanage permissive -a myapp_t
# 해제
$ sudo semanage permissive -d myapp_t
# 현재 Permissive도메인 목록
$ sudo semanage permissive -l이 상태로 한 번 정상 동작 흐름을 끝까지 돌려본 뒤, 쌓인 거부를 audit2allow로 모듈화 → Enforcing으로 복귀.
4. 모듈 작성과 등록 #
$ sudo ausearch -m AVC -ts recent | audit2allow -M mywebapp_extra
$ less mywebapp_extra.te # 사람 검토!
$ sudo semodule -i mywebapp_extra.pp5. 검증 #
$ sudo semodule -l | grep mywebapp
$ sudo semanage permissive -d myapp_t # Permissive 해제, Enforcing 복귀
# 운영 흐름 다시 돌려서 거부 안 뜨는지 확인흔한 함정 #
setenforce 0으로 끝내기 — 가장 흔한 사고. 다음 부팅 때 다시 Enforcing이 되니 미봉책. 정책 모듈로 굳혀야 합니다.- audit2allow 결과를 검토 없이 설치 — 공격자가 만든 거부를 그대로 허용해버리는 위험. 항상
.te를 사람이 읽고. setenforce 0한 채로 운영 — 로그에 거부가 안 쌓이지 않습니다 (Permissive는 거부 로그를 남깁니다, Disabled와 다름). 그래도 운영 환경에서 영구 Permissive는 SELinux의 의미를 없애는 셈.Disabled로 부팅한 뒤 다시 Enforcing으로 — 라벨이 맞지 않는 파일이 쌓여 시스템이 정상 동작 안 함./.autorelabel만들고 재부팅 필요.- 부울이 있는데 모르고 정책 모듈 작성 —
httpd_can_network_connect같은 표준 부울로 풀리는 문제는 부울로. 모듈 작성 전에getsebool -a | grep <키워드>로 검색. dontaudit때문에 거부가 안 보임 — 디버깅 중엔semodule -DB로 끄고, 끝나면semodule -B로 복원.restorecon누락 — 정책 등록만 하고 라벨 안 붙이면 효과 없음.restorecon -Rv <경로>항상 짝으로.- 모듈 이름 충돌 — 시스템에 같은 이름이 이미 있으면 덮어쓰여집니다.
myapp_extra,myapp_v2같이 명시적 이름으로.
기억해 둘 명령 #
| 작업 | 명령 |
|---|---|
| 거부 확인 | sudo ausearch -m AVC -ts recent |
| 친절한 해설 | sudo journalctl -t setroubleshoot / sealert -a |
| 거부 → 모듈 | audit2allow -M <name> (입력은 ausearch 출력) |
| 모듈 등록 | sudo semodule -i <name>.pp |
| 모듈 제거 | sudo semodule -r <name> |
| 모듈 목록 | sudo semodule -l |
| dontaudit 끄기/켜기 | sudo semodule -DB / sudo semodule -B |
| 도메인 Permissive | sudo semanage permissive -a/-d <type> |
| 부울 보기/변경 | getsebool -a / sudo setsebool -P <bool> on |
| 라벨 적용 | sudo restorecon -Rv <경로> |
| 정책 빌드 | make -f /usr/share/selinux/devel/Makefile <module>.pp |
| seinfo 탐색 | seinfo -t <type> / sesearch -A -s <type> |
정리 #
- 정책 파일은
.te(룰),.fc(라벨 매핑),.if(인터페이스) — 작은 모듈은.te하나로 충분합니다. - audit2allow — 거부 → 모듈을 자동 생성. 결과를 사람이 검토하고 등록. 새 type도입은 직접
.te로. - 컴파일 파이프라인 —
selinux-policy-devel의 Makefile 한 줄로.pp까지.semodule -i로 영구 등록. - 부울 — 정책 안의 스위치. 표준 부울로 풀리는 문제는 모듈 만들지 말고 부울로.
- 디버깅 흐름 — 거부 확인 →
semodule -DB로 dontaudit 끄기 →semanage permissive -a <type>로 격리 → 운영 흐름 → audit2allow 모듈화 → Enforcing 복귀. - 절대
setenforce 0으로 끝내지 말 것 — 늘 모듈로 굳히기.
다음 글은 SELinux 위에 올라가는 더 넓은 보안 강화 영역입니다. 시스템에 일어나는 모든 변화를 추적하는 auditd, 보안 표준 검사 자동화 OpenSCAP, 그리고 정부,금융 인증에서 요구되는 FIPS 모드까지 한 사이클로 다루겠습니다.