Linux - 데몬을 위한 SELinux 보안 정책 설정
이상하군요, 분명히 systemd에 등록한 서비스가 uid == 0으로 실행되고 있는데 RemoveMemlock 코드에서 이런 오류가 발생합니다.
// uid: 0, euid: 0, gid: 0
if err := rlimit.RemoveMemlock(); err != nil {
log.Fatal("Removing memlock:", err)
// Removing memlock:unexpected error detecting memory cgroup accounting: permission denied
}
"
Golang + bpf2go를 사용한 eBPF 기본 예제" 글에서 root 권한으로 실행하지 않으면 "failed to set memlock rlimit: operation not permitted" 오류가 발생한다고 했는데요, 이번에 오류 메시지가 다릅니다.
검색해 보면,
unable to set memory resource limits #20676
; https://github.com/cilium/cilium/issues/20676
I have the same problem, it's due to selinux. After I disabled selinux everything works.
SELinux 환경에서 발생한다고 하는데요, 실제로 저 오류가 발생한 CentOS 7 서버에 SELinux가 활성화돼 있었습니다.
5.3. Main Configuration File
; https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/security-enhanced_linux/sect-security-enhanced_linux-working_with_selinux-main_configuration_file
$ cat /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of three values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted
또는, 이렇게 확인할 수도 있습니다.
$ getenforce
Enforcing
$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Max kernel policy version: 31
이 상황을 해결하는 가장 단순한 방법은 /etc/selinux/config 파일에서 SELinux 설정을 비활성화하고,
SELINUX=disabled
재부팅을 하면 됩니다.
하지만, 테스트 환경을 제외한다면 실제 업무 서버에 SELinux가 설치된 경우 이를 비활성화하는 것은 현실적이지 않습니다. 그렇다면 SELinux 환경에 뭔가 예외 설정을 해야 할 것 같은데요, 좀 더 살펴보겠습니다. ^^
우선, SELinux가 설치된 환경에서도 systemd 데몬으로서가 아닌, 단순히 명령행에서 프로세스를 sudo로 실행하면 저런 오류가 발생하지 않는데요, 이 차이점은 실행된 상태의 프로세스 속성을 확인해 보면 알 수 있습니다.
// systemd로 실행된 상태인 경우,
$ ps -eZ
...[생략]...
system_u:system_r:unconfined_service_t:s0 10102 ? 00:00:00 test-myapp
...[생략]...
// sudo로 실행한 상태인 경우,
$ ps -eZ
...[생략]...
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 11700 pts/1 00:00:00 test-myapp
...[생략]...
보는 바와 같이 systemd로 실행된 상태에서 적용된 system_u의 경우 SELinux 정책에 등록된 사용자에 해당하고,
$ sudo semanage login -l
Login Name SELinux User MLS/MCS Range Service
__default__ unconfined_u s0-s0:c0.c1023 *
root unconfined_u s0-s0:c0.c1023 *
system_u system_u s0-s0:c0.c1023 *
$ seinfo -u
Users: 8
sysadm_u
system_u
xguest_u
root
guest_u
staff_u
user_u
unconfined_u
이에 대해 문서를 보면,
unconfined_u 사용자로 적용되지 않은 경우라면 SELinux 정책에 따라 제한이 걸린다고 하니 일단은 sudo/systemd로 실행하는 것에 대한 차이는 설명이 됩니다.
자, 그렇다면 이제 할 일은 system_u로 실행된 프로세스가 어떤 권한을 필요로 하는지 알아내야 하는데요, 바로 이런 정보를 SELinux의 audit log에서 확인할 수 있습니다.
$ sudo cat /var/log/audit/audit.log | grep -m 1 denied
...[생략]...
type=AVC msg=audit(1736234798.810:41215): avc: denied { map_create } for pid=2227 comm="my-test-app" scontext=system_u:system_r:unconfined_service_t:s0 tcontext=system_u:system_r:unconfined_service_t:s0 tclass=bpf permissive=0
이 외에도 audit log를 정제해서 보여주는 다양한 SELinux 도구들이 있으므로 이를 활용해도 됩니다.
// audit.log 파일에서 검색 힌트를 얻을 수 있는데,
// 문제가 되는 유형은 comm="my-test-app" and msg type == "avc"이고,
// --start 옵션으로 특정 시간 이후에 생성된 로그로 제한할 수 있습니다.
$ sudo ausearch --start 01/09/2025 00:00:00 -m avc --raw --comm my-test-app
...[생략]...
type=AVC msg=audit(1736408283.758:43684): avc: denied { map_create } for pid=14028 comm="my-test-app" scontext=system_u:system_r:unconfined_service_t:s0 tcontext=system_u:system_r:unconfined_service_t:s0 tclass=bpf permissive=0
$ sealert -l "*"
...[생략]...
SELinux is preventing my-test-app from map_create access on the bpf labeled unconfined_service_t.
***** Plugin catchall (100. confidence) suggests **************************
If you believe that my-test-app should be allowed map_create access on bpf labeled unconfined_service_t by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'my-test-app' --raw | audit2allow -M my-se-policy
# semodule -i my-se-policy.pp
Additional Information:
Source Context system_u:system_r:unconfined_service_t:s0
Target Context system_u:system_r:unconfined_service_t:s0
Target Objects Unknown [ bpf ]
Source my-test-app
Source Path my-test-app
Port
Host centos7
Source RPM Packages
Target RPM Packages
Policy RPM selinux-policy-3.13.1-229.el7_6.12.noarch selinux-
policy-3.13.1-268.el7_9.2.noarch
Selinux Enabled True
Policy Type targeted
Enforcing Mode Enforcing
Host Name centos7
Platform Linux centos7 5.1.15-1.el7.elrepo.x86_64 #1 SMP
Tue Jun 25 20:52:45 EDT 2019 x86_64 x86_64
Alert Count 3
First Seen 2025-01-09 19:36:13 KST
Last Seen 2025-01-09 19:37:50 KST
Local ID c15423a2-e00f-470a-9f9e-67a3c93632d6
Raw Audit Messages
type=AVC msg=audit(1736408270.825:43673): avc: denied { map_create } for pid=13979 comm="my-test-app" scontext=system_u:system_r:unconfined_service_t:s0 tcontext=system_u:system_r:unconfined_service_t:s0 tclass=bpf permissive=0
Hash: my-test-app,unconfined_service_t,unconfined_service_t,bpf,map_create
sealert의 경우 첫 번째 라인에 나오는 메시지가 모든 것을 설명하는데요,
SELinux is preventing my-test-app from map_create access on the bpf labeled unconfined_service_t.
즉, unconfined_service_t로 분류된 comm="my-test-app" 프로세스가 bpf에 대한 map_create 접근을 시도했지만, SELinux 정책에 의해 거부당했다는 것을 의미합니다.
자, 이제 원인을 알았으니 SELinux 정책을 수정해야 하는데요, 재미있게도 audit 로그 파일의 내용을 기반으로 정책을 만들어 주는
audit2allow 도구를 제공하고 있어 이 단계를 쉽게 해결할 수 있습니다.
// Carbon Black Cloud: How to allow BPF event collection on SELinux
// https://knowledge.broadcom.com/external/article/292463/carbon-black-cloud-how-to-allow-bpf-even.html
$ sudo ausearch -c 'my-test-app' --raw | audit2allow -M my-se-policy
******************** IMPORTANT ***********************
To make this policy package active, execute:
semodule -i my-se-policy.pp
// 혹은, 특정 시간을 지정해서,
$ sudo ausearch --start 01/13/2025 00:00:00 -c 'my-test-app' --raw | audit2allow -M my-se-policy
위의 명령어를 실행하면 my-se-policy.pp, my-se-policy.te 2개의 파일이 생성되는데요, te(type enforcement) 파일이 텍스트 유형의 설정 파일이고 그것을 컴파일한 결과물이 pp(policy package)입니다.
te 파일을 보면,
$ cat my-se-policy.te
module my-se-policy 1.0; # 이곳의 모듈 이름은 반드시 파일명과 일치해야 합니다.
require {
type unconfined_service_t;
class bpf map_create;
}
#============= unconfined_service_t ==============
allow unconfined_service_t self:bpf map_create;
대략 bpf에 대한 map_create 접근을 unconfined_service_t로 분류된 프로세스에 허용하는 설정임을 짐작게 합니다. 따라서 별다르게 수정할 것이 없다면 이대로 SELinux 정책에 다음과 같은 명령어로 반영하면 됩니다.
$ sudo semodule -i my-se-policy.pp
$ sudo semodule -l | grep my-se-policy
my-se-policy 1.0
// 만약 정책을 삭제하고 싶다면,
$ sudo semodule -r my-se-policy
libsemanage.semanage_direct_remove_key: Removing last my-se-policy module (no other my-se-policy module exists at another priority).
끝입니다, 이후 systemd로 등록된 데몬을 실행하면 오류 없이 잘 동작합니다. ^^
혹시나 te 파일을 수정했다면 다시 컴파일해서 pp 파일을 생성해야 합니다. 이를 위해 make 명령어를 사용할 수 있는데요,
$ make -f /usr/share/selinux/devel/Makefile my-se-policy.pp
Compiling targeted my-se-policy module
/usr/bin/checkmodule: loading policy configuration from tmp/my-se-policy.tmp
/usr/bin/checkmodule: policy configuration loaded
/usr/bin/checkmodule: writing binary representation (version 19) to tmp/my-se-policy.mod
Creating targeted my-se-policy.pp policy package
rm tmp/my-se-policy.mod.fc tmp/my-se-policy.mod
위의 명령어는 현재 디렉터리에서 (pp 파일명으로 지정한) my-se-policy.te 파일을 찾아 빌드해 pp 파일을 생성합니다. 또 다른 방법으로는,
pp 파일을 make가 아닌 SELinux 도구로 생성하는 방법이 있는데요,
$ checkmodule -M -m -o my-se-policy.mod my-se-policy.te
checkmodule: loading policy configuration from my-se-policy.te
checkmodule: policy configuration loaded
checkmodule: writing binary representation (version 19) to my-se-policy.mod
위와 같이 실행하면 my-se-policy.mod 파일이 생성되는데, 이것으로부터 pp 파일을 생성할 수 있습니다.
$ semodule_package -o my-se-policy.pp -m my-se-policy.mod
아마도, C/C++ 언어와 같은 빌드 단계로 따지자면 te 파일은 소스코드, mod 파일은 오브젝트 파일, pp 파일은 실행 모듈에 해당하는 것 같습니다.
참고로, SELinux가 적용된 환경이어도 관련 도구들이 없는 경우가 많을 것입니다. 따라서, 각각의 도구에 따라 관련 패키지를 별도로 설치해야 합니다.
$ semanage
-bash: semanage: command not found
// semanage command not found in CentOS 7 / 6 & RHEL 7 / 6 – Quick Fix
// https://www.itzgeek.com/how-tos/linux/centos-how-tos/semanage-command-not-found-in-centos-7-rhel-7-quick-fix.html
$ sudo yum -y install policycoreutils-python
$ seinfo -u
-bash: seinfo: command not found
$ sudo yum -y install setools-console
$ sealert -l "*"
-bash: sealert: command not found
$ sudo yum -y install setroubleshoot
$ sepolicy generate --init /usr/bin/remon_app/my-test-appion-linux-amd64
-bash: sepolicy: command not found
$ sudo yum -y install selinux-policy-devel
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]