운영계 세팅 회고 ep4 - SELinux란?
목차
English version: Production Server Setup Notes, Ep. 4 - What Is SELinux?
개요
운영 서버는 SELinux를 Enforcing mode로 두기로 했다.
개발 서버는 Permissive mode라서, 개발 서버에서 문제없던 컨테이너가 운영 서버에서는 Permission denied로 막히게 된다.
SELinux가 적용되면, chmod로 파일 권한을 열어 줘도 접근이 막힐 수 있다.
파일 권한과 별개로 접근을 한 번 더 검사하기 때문이다.
DAC와 MAC
리눅스의 기본 권한은 DAC(Discretionary Access Control)다.
파일 소유자가 rwx로 누구에게 무엇을 허용할지 정한다.
SELinux는 그 위에 MAC(Mandatory Access Control)을 하나 더 얹는다.
허용 여부를 파일 소유자가 아니라 시스템 정책이 정하고, root도 이 검사를 피하지 못한다.
SELinux가 켜져 있으면 모든 접근 요청은 DAC를 통과한 뒤 SELinux 정책까지 통과해야 허용된다.
라벨: SELinux가 보는 것
SELinux는 모든 프로세스와 파일에 라벨(context)을 붙인다.
라벨은 user:role:type:level 네 칸이고, ls -Z(파일), ps -Z(프로세스)로 볼 수 있다.
판단은 대부분 세 번째 칸인 type으로 한다.
정책은 “이 type의 프로세스는 저 type의 파일에 이런 동작을 해도 된다”는 허용 목록이다.
이 방식을 type enforcement라고 부른다.
정책에 허용 규칙이 없는 동작은 전부 거부된다.
세 가지 mode
| mode | 동작 |
|---|---|
| Enforcing | 정책을 기준으로 허용/거부 && 거부는 기록 |
| Permissive | 정책 위반도 허용 && 위반은 기록 |
| Disabled | 검사 X |
서버가 어떤 mode로 돌고 있는지는 getenforce 명령으로 확인한다.
Permissive에서는 정책 위반이 기록만 되고 실제로는 막히지 않는다.
그래서 개발 서버가 Permissive이면 운영 서버(Enforcing)에서 막힐 동작이 개발 단계에서는 드러나지 않는다.
setenforce로 바꾼 mode는 재부팅하면 되돌아간다. 영구 설정은 /etc/selinux/config에서 한다.
거부(denial) 확인
SELinux가 막은 동작은 감사 로그(audit log)에 AVC 타입의 거부 기록으로 남는다.
ausearch -m AVC -ts today로 오늘 막힌 기록을 볼 수 있다.
AVC(Access Vector Cache)는 SELinux가 "이 type이 저 type에 이 동작을 해도 되는가"라는 판단 결과를 캐시해 두는 곳이다. 거부 기록에 이 이름이 붙는다.
avc: denied { read } for pid=4121 comm="otelcol" name="pods"
scontext=system_u:system_r:container_t:s0:c12,c34
tcontext=system_u:object_r:container_log_t:s0 tclass=dir
scontext: 누가(요청한 프로세스의 라벨)tcontext: 무엇을(대상 파일의 라벨){ read },tclass=dir: 어떤 동작을, 어떤 종류의 대상에
이 예는 컨테이너(container_t)가 파드 로그 디렉터리(container_log_t)를 읽으려다 막힌 기록이다.
정책에는 일부러 기록하지 않는 거부 규칙(dontaudit)도 있다. 막혔는데 AVC 기록이 없으면 이 경우를 의심한다.
거부(denial) 해결
Red Hat 공식 문서는 SELinux 거부(denial)를 해결할 때 라벨 문제부터 확인하고, 그다음 boolean, 마지막으로 정책 모듈 순으로 내려가라고 안내한다.
뒤로 갈수록 허용 범위가 넓어진다.
1. 라벨 바로잡기
대부분은 파일에 엉뚱한 라벨이 붙어 있어서 막힌다.
semanage fcontext로 경로별 라벨 규칙을 등록하고 restorecon으로 적용한다.
semanage fcontext -a -t container_file_t "/srv/data(/.*)?"
restorecon -Rv /srv/data
chcon으로도 라벨을 바꿀 수 있지만, 규칙으로 등록되지 않아 나중에 restorecon이나 전체 재라벨링을 하면 원래대로 돌아간다.
2. boolean 켜기
정책에는 자주 필요한 허용을 미리 만들어 둔 스위치(boolean)가 있다.
예를 들어 웹 서버가 외부로 접속해야 하면 setsebool -P httpd_can_network_connect on으로 켠다.
목록은 getsebool -a로 본다.
3. 정책 모듈 추가
라벨과 boolean으로 안 되면 막힌 기록으로 정책 모듈을 만든다.
audit2allow가 AVC 기록을 읽어 허용 규칙을 만들어 주고, semodule -i로 설치한다.
다만 기록에 남은 동작을 통째로 허용하므로, 만들어진 규칙을 읽어 보고 필요한 것만 남겨야 한다.
SELinux를 끄거나 Permissive로 내리는 것은 해결 방법이 아니다.
문제를 찾는 동안 잠깐 쓰더라도, 원인을 찾으면 위 셋 중 하나로 해결하고 Enforcing으로 되돌린다.
컨테이너와 SELinux
컨테이너 안에서 도는 프로세스에는 container_t라는 type이 붙는다.
정책은 container_t 프로세스가 container_file_t type의 파일을 읽고 쓰도록 허용한다.
그래서 서버(호스트)의 디렉터리를 볼륨으로 컨테이너에 연결했을 때, 그 디렉터리의 type이 container_file_t가 아니면 rwx 권한이 열려 있어도 접근이 거부된다.
예를 들어 새로 붙인 디스크의 디렉터리는 type이 unlabeled_t(라벨 없음)라서 컨테이너가 읽지도 쓰지도 못한다.
운영 서버를 세팅하며 실제로 겪은 것은 세 가지였다.
- 데이터 디스크 폴더에 라벨이 없었다. 따로 붙인 디스크의 폴더가 전부
unlabeled_t라 컨테이너가 쓸 수 없었다. 1번 방법으로 DB와 백업 폴더에container_file_t규칙을 등록했다. - 로그 수집기가 파드 로그를 읽지 못했다. 거부(denial) 확인에서 본 AVC 예시가 이 경우다. 아무 제한도 없는
spc_t대신, 컨테이너 로그 읽기만 더 허용하는container_logreader_t로 수집기를 돌렸다. - postStart 훅이 컨테이너 로그로 출력하지 못했다. 파드가 뜰 때 실행되는 postStart 훅이
/proc/1/fd/1에 쓰려다 막혔고, 출력을 파드 안의 파일로 바꿨다.
Docker와 Podman에서는 볼륨 옵션 :z(여러 컨테이너가 공유)나 :Z(그 컨테이너 전용)를 붙이면 붙이는 폴더의 라벨을 알아서 바꿔 준다.
k3s는 SELinux 정책 패키지(k3s-selinux)를 따로 제공하고, 설정에 selinux: true를 넣어 켠다.
k3s 공식 문서는 SELinux를 켠 상태에서 --data-dir로 데이터 위치를 바꾸는 것을 지원하지 않는다고 적고 있다.
그래서 데이터 위치를 바꾸는 대신, k3s 기본 데이터 경로(/var/lib/rancher)에 데이터 디스크의 디렉터리를 바인드 마운트했다.
그리고 semanage fcontext -e로 데이터 디스크 쪽 디렉터리도 기본 경로와 같은 라벨을 받도록 등록했다.
이렇게 해 두면 누가 데이터 디스크 쪽에 restorecon을 돌려도 k3s 라벨이 깨지지 않는다.
참고
- Red Hat, Using SELinux (RHEL 9) https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/using_selinux/index
- K3s, SELinux Support https://docs.k3s.io/advanced#selinux-support
- containers/container-selinux https://github.com/containers/container-selinux