RHEL 고급 #4 SELinux 고급 — 정책 직접 작성과 audit2allow

8 분 소요

중급 #1 SELinux 입문에서 모드, 라벨, AVC 거부, audit2allow로 풀어가는 흐름까지 봤습니다. 이번 글은 그 위 한 층입니다. audit2allow가 만들어주는 결과물의 정체가 무엇인지, 그게 부족할 때 직접 .te 파일을 손으로 작성해서 컴파일하는 절차, 정책 모듈을 시스템에 영구 등록해서 다음 부팅 후에도 살아남게 만드는 흐름까지 한 사이클로 다루겠습니다. 거부가 보일 때마다 setenforce 0으로 도망가지 않고 끝까지 따라가서 모듈로 굳히는 게 목표입니다.

RHEL 고급 시리즈에서 이번 글의 위치:

정책 파일의 정체 — .te / .fc / .if #

SELinux 정책 모듈은 다음 세 종류의 소스 파일로 구성됩니다.

파일무엇을 담나
*.teType Enforcement — allow 룰, type 선언
*.fcFile Contexts — 어떤 경로에 어떤 라벨을 붙일지
*.ifInterface — 다른 모듈에서 호출할 수 있는 매크로 (선택)

.te가 핵심입니다. .fcrestorecon이 참고할 라벨 매핑이고, .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의 구조 #

mywebapp.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 정책을 부여해 보겠습니다.

myapp.te
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으로 등록

파일 컨텍스트 정의 #

myapp.fc
/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
빌드 — Makefile 한 줄
$ 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 myapp

semodule -rmyapp.pp가 아니라 모듈 이름 을 받습니다. .pp 확장자 빼고.

type, attribute, allow의 문법 #

type과 attribute #

type은 객체에 붙이는 라벨 자체이고, attribute는 type 들의 묶음입니다.

attribute 예
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_contextsseinfo 명령으로 확인.

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가 영구 적용. 안 주면 재부팅 시 사라집니다.

자기 모듈에 부울 박기 #

myapp.te에 부울 추가
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.log

setroubleshoot가 가장 도움이 됩니다. 거부가 일어난 원인 후보와 보통의 해결책을 자연어로 설명합니다.

2. dontaudit 룰 때문에 안 보일 때 #

기본 정책에는 시끄러운 거부를 숨기는 dontaudit 룰이 많습니다. 디버깅 중엔 임시로 끄세요.

dontaudit 임시 비활성화
$ sudo semodule -DB

# 디버깅 끝나면 다시 활성화
$ sudo semodule -B

3. Permissive도메인으로 격리 #

전체 시스템을 Permissive로 떨어뜨리지 말고, 의심 도메인만 Permissive로:

특정 도메인만 Permissive
$ sudo semanage permissive -a myapp_t

# 해제
$ sudo semanage permissive -d myapp_t

# 현재 Permissive도메인 목록
$ sudo semanage permissive -l

이 상태로 한 번 정상 동작 흐름을 끝까지 돌려본 뒤, 쌓인 거부를 audit2allow로 모듈화 → Enforcing으로 복귀.

4. 모듈 작성과 등록 #

audit2allow → 모듈
$ sudo ausearch -m AVC -ts recent | audit2allow -M mywebapp_extra
$ less mywebapp_extra.te         # 사람 검토!
$ sudo semodule -i mywebapp_extra.pp

5. 검증 #

제대로 들어갔나
$ 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
도메인 Permissivesudo 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 모드까지 한 사이클로 다루겠습니다.

X