SELinux 문제 해결(TroubleShooting)


이제 마지막으로 SELinux 에서 서비스나 프로그램이 제대로 동작하지 않을 때 원인을 분석하고 해결책을 찾는 과정을 살펴보면서 앞에서 설명한 내용을 정리해 보겠습니다.


아래 그림은 일반적인 SELinux 문제 발생시 해결 절차를 기술한 그림으로 문제 발생시 제일 먼저 할 일은 로그 분석입니다.


로그 분석

프로그램이나 서비스가 제대로 동작하지 않을 때 사용자들은 매우 당황하기 마련이며 특히 SELinux는 차단한 어플리케이션에게 자세한 에러 메시지를 전달하지 않고 차단 로그는 별도의 로그로 저장합니다.

이는 자세한 에러 메시지가 전달되면 공격자가 이 내용을 참고하여 공격시 악용할 수 있기 때문이지만 반대로 경험이 부족할 경우 로그 파일의 위치도 찾기도 어려워서 원인 파악 단계부터 어려움에 부딪힐 수 있습니다.

특히 리눅스의 어떤 로그 데몬을 사용하는지에 따라 로그 파일의 위치가 달라질 수 있으므로 다음 내용을 참고해서 로그 내용을 분석해야 합니다.

  • auditd 사용
    /var/log/audit/audit.log 에 SELinux 로그가 남게 됩니다. audit.log 에는 SElinux 뿐만 아니라 사용자 로그인/로그오프, 데몬 시작/종료등 중요한 시스템의 감사 이력이 저장됩니다.
  • rsyslogd 사용
    auditd 를 사용하지 않고 rsyslogd 를 사용할 경우 시스템 로그 파일인 /var/log/messages 에 SELinux 의 감사 로그도 남게 되므로 문제 발생시 이 파일을 확인해야 합니다.
  • auditd + setroubleshootd 사용
    audit.log 에는 감사 이력이 저장되며 SELinux 의 에러 메시지는 사용자 친화적으로 번역되어 /var/log/messages 에 저장됩니다.


이제 SELinux 의 로그 파일의 위치를 찾았으면 그 안에서 로그 메시지를 확인할 차례입니다. 로그 파일에는 SELinux 가 차단한 명령어와 에러 상황이 기록되어 있지만 다른 프로그램이나 데몬이 남기는 로그도 같이 포함되어 있을수 있습니다. 그러므로 SELinux 관련 로그만 뽑아 내려면 grep 등의 명령어로 필터링을 해야 하며 이를 위해서는 로그 패턴을 알아야 합니다.

audit.log 와 messages 에 남는 SELinux 의 에러 로그 형식은 약간 다르며 다음 명령어로 로그 파일에서 필요한 내용을 필터링할 수 있습니다.

$ sudo grep "SELinux is preventing" /var/log/messages  
$ sudo grep "type=AVC" /var/log/audit/audit.log
grep 으로 SELinux 로그 출력 하기


grep 을 사용하여 매번 각각의 로그 파일에서 필터링 하는건 번거로울 수 있으므로 audit daemon 절에서 설명한 audit 로그를 분석해 주는 유틸리티인 aureport 를 -a 옵션을 주고 실행하면 SELinux 의 에러 로그를 좀 더 간편하게 확인할 수 있습니다.


너무 많은 로그가 출력된다면 로그의 시작일을 지정하는 --ts 옵션을 사용하여 특정 기간의 로그만 출력할 수 있습니다. -ts 옵션 뒤에는  "2017년11월31일" (리눅스의 LANG 변수가 ko_KR.UTF-8 일 경우) 와 같이 시작 날자를 지정할 수 있습니다.

다음은 17년 11월 31일 이후에 발생한 SELinux 로그만 출력합니다.

$ sudo aureport -a -ts "2017년11월31일"


denied 306
48. 2017년 12월 9일 12:01:01 ? system_u:system_r:init_t:s0 0 (null) (null) (null) unset 466
49. 2017년 12월 9일 12:11:03 ? system_u:system_r:init_t:s0 0 (null) (null) (null) unset 487
50. 2017년 12월 10일 11:19:28 nginx system_u:system_r:httpd_t:s0 2 file read unconfined_u:object_r:admin_home_t:s0 denied 136
51. 2017년 12월 10일 11:22:00 nginx system_u:system_r:httpd_t:s0 2 file read unconfined_u:object_r:admin_home_t:s0 denied 137
aureport 로 SELinux 로그 조회

만약 LANG 변수가 en_US.utf8 등의 영어로 설정 되어 있다면 날자를 11/31/2017 처럼 로캘(locale)에 맞게 입력해야 합니다.

$ sudo aureport -a -ts "11/31/2017"
aureport 로 SELinux 로그 조회


만약 grep 이나 aureport 로 검색해 보아도 SELinux 로그가 없다면나 setenforce 0 으로 permissive 모드로 변경한 후에 에러가 재연되는지 확인해 봅니다.

permissive 로 변경후 정상 동작한다면 SELinux 때문이므로 이제 해결 단계로 넘어가면 됩니다.

해결 단계

SELinux 로 인해 서비스에 문제가 생기는 경우는 주로 다음과 같은 이유때문이므로 순서대로 확인하면서 문제를 해결해 나가야 합니다.


잘못된 레이블

가장 많이 하는 실수로 잘못된 보안 컨텍스트를 부여해서 발생합니다. 예로 웹 서버가 읽어야 하는데  httpd_sys_content_t 를 부여하지 않았거나 써야 하는데 httpd_sys_rw_content_t 않은 경우이며 cp 대신 mv 명령을 사용하여 발생하는 경우가 많습니다.

차단된 자원의 적절한 컨텍스트는 무엇인지 확인하기 위해 컨텍스트 다루기에서 소개한 matchpathconchcon, restorecon 등의 유틸리티를 사용하여 문제를 해결합니다.


만약 사전에 정의된 경로가 아닌 새로운 경로를 웹 서버등에 등록해야 한다면 semanage fcontext 명령어를 사용합니다. 

$ sudo semanage fcontext -a -t httpd_sys_content_t "/myweb(/.*)?"
/myweb 하위 경로에 자동으로 httpd_sys_content_t 부여


제한적 서비스로 인한 기능 차단

SELinux 의 철학상 1024 이하 포트를 사용하는 (루트로 구동되는) 서비스는 엄격하게 통제하므로(이를 Confined Service 라고 부릅니다.) 사전에 허용된 동작만 가능합니다.

이때문에 PHP 나 Python 를 웹 서버의 모듈 방식으로 사용할 경우 거의 대부분의 기능이 차단됩니다. 예로 웹서버를 해킹해서 스팸 메일을 보내는 경우가 많기때문에 httpd 는 sendmail 이나 postfix 같은 메일 서버에 연결이 차단되어 있으며 MySQL 같은 DBMS 에도 연결할수 없으며 사용자의 홈 디렉터리도 읽을 수 없습니다.


SELinux boolean

이때문에 문제가 생긴다면 SELinux Boolean 을 수정하여 해결할수 있으며 예로 아파치 웹 서버에서 사용자 홈 디렉터리를 통해 웹 서비스를 제공하려면 아파치의 UserDir  디렉터리 설정을 켜고 아래 boolean 도 켜야 합니다.

$ sudo setsebool -P httpd_enable_homedirs on
웹 서버의 사용자 홈 디렉터리 접근 허용

많은 SELinux boolean 중에 서비스와 관련한 boolean 을 찾으려면 getsebool -a 로 모든 boolean 을 표시한 후에 grep 으로 필터링하는 것입니다.


예로 다음 예제는 samba 와 관련된 boolean 을 표시해 줍니다.

$ sudo getsebool -a | grep samba


samba_create_home_dirs --> off
samba_domain_controller --> off
samba_enable_home_dirs --> off
samba_export_all_ro --> off
samba_export_all_rw --> off
samba_load_libgfapi --> off
samba 관련된 boolean 목록


개별 boolean 의 의미를 이해하기 어렵다면 semanage boolean -l 명령으로 의미를 출력할 수 있으며 다음 명령은 httpd 와 관련된 boolean 의 의미와 기본 설정, 현재 설정을 출력합니다.


$ sudo semanage boolean -l |grep httpd


httpd_can_network_relay        (off  ,  off)  Allow httpd to can network relay
httpd_can_connect_mythtv       (off  ,  off)  Allow httpd to can connect mythtv
httpd_can_network_connect_db   (on   ,   on)  Allow httpd to can network connect db
httpd_use_gpg                  (off  ,  off)  Allow httpd to use gpg
httpd_dbus_sssd                (off  ,  off)  Allow httpd to dbus sssd
httpd_enable_cgi               (on   ,   on)  Allow httpd to enable cgi
httpd_verify_dns               (off  ,  off)  Allow httpd to verify dns
웹 서버 관련한 boolean 목록과 의미 출력


허가되지 않은 네트워크 포트

SELinux 에서는 데몬 프로세스의 네트워크 포트 접근을 엄격하게 통제하므로 사전에 허가받은 포트외에는 접속할 수 없습니다.

예로 아파치 httpd 는 웹서비스용 포트인 80, 443과 tomcat 이 사용하는 포트인 8009, 8443, PHP 가 사용하는 9,000 번 포트같은 많이 사용하는 서비스 포트만 연결할 수 있으며 이런 사전 탑재된 "강제 접근 통제" 정책 덕분에 웹 서버가 해킹당해도 이를 이용해 다른 서버로 ssh 를 침투하는 등의 2차 피해를 방지할 수 있습니다.


하지만 여러 가지 이유로 웹 서버를 일반적이지 않은 포트에 띄우거나 서비스가 늘어나서 tomcat 등의 WAS 인스턴스를 확장해서 포트가 늘어날 경우 SELinux 는 사전 허용된 포트가 아니므로 차단하게 됩니다.

만약 새로운 포트 번호에 연결할 수 없다면 사용하려는 포트가 허가된 포트인지를 우선적으로 확인하고 없으면 추가해 주면 되며 이는 보안 정책 관리 에서 설명한 semanage 명령어로 처리할 수 있습니다.


다음 명령어는 웹 서버가 연결 가능한 포트 번호 목록을 확인합니다.

$ sudo semanage port -l | grep http_port_t

http_port_t                    tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000
웹 서버가 연결 가능한 포트 번호 확인


만약 새로운 WAS 가 9009 포트를 사용한다면 다음 명령어로 추가해 주면 됩니다.

$ sudo semanage port -a -p tcp -t http_port_t 9009
웹 서버가 연결 가능한 포트 번호 확인

 만약 "Port tcp/9009 already defined" 처럼 이미 9009 포트에 다른 컨텍스트가 할당되었다면 포트 번호를 추가하는 옵션인 -a 대신 변경하는 옵션인 -m 을 사용합니다.



도메인 허용

운영 시스템이거나 기타 시급하게 문제를 우선 처리하고 원인 분석은 나중에 해야 할 경우가 있을 수 있으며 이럴 경우 유용한 방법은 문제가 되는 도메인만 permissive 로 전환하는 방법입니다.

예로 웹 서버가 특정 기능만 수행하지 못해서 서비스에 문제가 생겼는데 원인이 SELinux 정책 위반으로 의심될 경우 장애 상황이므로 문제를 차근 차근 찾아서 해결하는 것보다는 서비스 제공이 우선일 수 있습니다.


도메인을 허용하려면 semanage permissive 명령을 사용하며 -a 뒤에 허용할 도메인을 적어주면 되며 다음은 httpd_t 도메인에 속한 프로세스의 모든 동작을 허용합니다. 

$ sudo semanage permissive -a httpd_t
httpd_t 도메인 허용

현재 permissive 된  도메인을 확인할 경우 semodule -l 명령과 grep 을 사용하면 되며 다음 명령은 httpd_t 도메인의 허용 여부를 출력해 줍니다.

$ sudo  semodule -l | grep permissive

permissive_httpd_t      (null)
permissivedomains       (null)
httpd_t 도메인의 허용 여부 확인

현재 허용 모드일 경우 위와 같이 목록에 표시가 됩니다. 원인 분석과 조치가 끝나서 해당 도메인을 다시 enforce 모드로 전환할 경우 다음과 같이 -d 옵션을 사용하여 허용 목록에서 삭제해 주면 됩니다.


$ sudo semanage permissive -d httpd_t
httpd_t 도메인 enforce 모드 전환



SELinux 정책 변경

새로운 데몬 어플리케이션 출시 및 기존 어플리케이션의 변경에 맞게 SELinux 의 보안 정책도 유연하게 변경할수 있게 모듈화되어 있고 selinux-policy, selinux-policy-targeted 라는 패키지명으로 제공되고 있습니다. 

하지만 보안 정책에 버그가 있어서 이로 인해 원치않은 프로그램이 차단될 수 있으며 또는 새 버전의 어플리케이션에 추가된 기능이 기존 SELinux 의 정책에 없는 자원에 접근하려고 해서 차단될 수도 있습니다.

보안 정책도 주기적으로 업데이트가 출시되므로 yum 으로 패키지 업데이트를 하면 해결될 수도 있지만 아닌 경우 SELinux 를 끄지 말고 문제가 되는 프로세스만 SELinux 에서 제한없이 실행되게 설정하는 것을 권장합니다.


unconfined 설정

다음과 같이 chcon 으로 문제가 되는 프로세스에 unconfined_exec_t  보안 컨텍스트를 부여하면 SELinux 가 프로세스의 권한을 제한하지 않습니다.

$ sudo chcon -t unconfined_exec_t /usr/sbin/httpd
httpd_t 도메인 enforce 모드 전환


audit2allow 사용

마지막으로 적용해볼 방법은 SELinux 에 새로운 허용 정책을 추가할 수 있는 유틸리티인 audit2allow 를 활용한 해결 방법입니다.

audit2allow 는 audit2why로 SELinux 에러 조회하기 에서 다룬 audit2why 의 자매 유틸리티로 audit log 에서 정보를 취합하여 문제가 된 프로세스에 맞게 새로운 보안 정책을 추가할 수 있게 해줍니다.


audit2allow 로 문제를 해결하려면 먼저 에러 상황을 조회해야 하며 audit 에 있는 메시지를 읽기 쉽게 표시해 주는 옵션인 -w (--why) 와 audit.log 에서 읽어 들이는 옵션인 -a 를 사용하면 됩니다. 


다음은 브라우저가 요청한 hello.html 이 잘못된 컨텍스트인 user_home_t 가 설정되어 있어서 웹 서버가 읽지 못했을 때의 에러 메시지입니다.

$ sudo audit2allow -w -a


type=AVC msg=audit(1513407744.234:455): avc:  denied  { read } for  pid=899 comm="nginx" name="hello.html" dev="xvda3" ino=208212244 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:user_home_t:s0 tclass=file
        Was caused by:
        The boolean httpd_read_user_content was set incorrectly. 
        Description:
        Allow httpd to read user content

        Allow access by executing:
        # setsebool -P httpd_read_user_content 1
audit2allow 로 에러 목록 조회


이제 에러를 기반으로 허용 정책을 확인하려면 -a 옵션만 주고 실행합니다.

$ sudo audit2allow -a


#============= httpd_t ==============

#!!!! This avc is allowed in the current policy
allow httpd_t hugetlbfs_t:file write;

#!!!! This avc can be allowed using the boolean 'httpd_read_user_content'
allow httpd_t user_home_t:file read;
audit2allow 로 에러 목록 조회

위와 같이 SELinux 정책을 확인했으면 정책 파일을 생성할 차례입니다. -M filename 옵션을 사용하면 Type Enforcement 정책 파일인 filename.te 와 컴파일된 정책 패키지 파일인 filename.pp 2 가지 파일이 생성됩니다.


다음 예제는 차단 이력을 조회해서 이를 허용하는 정책 파일을 httpd-userhome-allow.te, httpd-userhome-allow.pp 로 생성합니다.

$ sudo audit2allow -a -M  httpd-userhome-allow

******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i httpd-userhome-allow.pp
audit2allow 로 허용 정책 파일 생성


httpd-userhome-allow.te 를 편집기로 열어보면 다음과 같이 생성된 허용 정책을 확인할 수 있으며 반영하려면 위 메시지대로 semodule -i  명령을 사용하면 됩니다.

$ sudo semodule -i httpd-userhome-allow.pp
생성한 허용 정책 반영



위 예제는 새로운 SELinux 정책을 생성하고 반영하는 예제일 뿐이며 audit2allow 로 정책 추가시 실제보다 많은 권한을 생성하므로 생성한 정책을 주의깊게 검토하고 적용해야 합니다.


 


마치며

SELinux 가 구현한 "강제 접근 통제" 정책은 중요한 시스템을 구성할 때 알아두어야 하는 보안 지식이지만 그간 참고할 자료가 별로 없어서 SELinux 의 중요성이 많이 간과된 것 같습니다.

모든 컴퓨터가 인터넷에 연결된 요즘, 보안은 고객과 비즈니스를 지켜주는 핵심 프로세스이고 SELinux 는 리눅스 서버의 믿음직한 보초병이지만 운영자를 불편하고 귀찮게 하며 서비스 장애를 유발한다는 널리 퍼진 잘못된 선입관을 수정해 보려고 한 장을 할애했습니다.


이 장을 읽은 독자들은 이제 SELinux 의 개념과 동작 방식을 이해하고 기본적인 사용 방법 및 문제 해결 절차에 대해 이해했으리라 기대합니다.

이제 SELinux 는 리눅스를 서비스 서버로 구성할 경우 반드시 켜 놓고 사용해야 하는 커널의 중요 보안 기능이라는 점을 인식하고, SELinux 의 강력한 시스템 보호 기능으로 리눅스 서버를 견고하게 만들어 안전하게 활용하기를 바랍니다.