Create SELinux rules from audit log

While writing build WriteFreely from source walkthrough I noticed that CentOS 7 (or any other Red Hat Enterprise Linux flavored distributions) which comes with SELinux enabled by default will have problems reverse-proxying visitor requests going from NGINX to web application - in this case compiled WriteFreely binary. But don’t rush to disable SELinux (common rookie mistake) - it’s actually easy to create rules right from audit log.

As soon as server block is configured in NGINX (running on port 8080 in this example) to reverse-proxy traffic to web application, error log will start showing these:

2019/03/02 01:19:46 [crit] 4508#0: *1 connect() to 127.0.0.1:8080 failed (13: Permission denied) while connecting to upstream, client: xxx.xxx.xxx.xxx, server: inret.io, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8080/",host: "inret.io"

With inret.io being server_name configured at server block and xxx.xxx.xxx.xxx being visitor’s IP address. Errors having “Permission denied” typically invites to check audit log located at /var/log/audit/audit.log and provides additional details of what exactly happened:

type=AVC msg=audit(1551482556.752:6031): avc:  denied  { name_connect } for  pid=4576 comm="nginx" dest=8080 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket permissive=0

We can see clues to check HTTP daemon - specifically NGINX. An exception is needed to be created with privileged user permissions:

setsebool httpd_can_network_connect on -P

This will allow NGINX to connect, however, to reverse-proxy traffic to web application this won’t be enough. When I was building WriteFreely on CentOS 7, more errors would be visible in error log at /var/log/nginx/error.log:

2020/04/02 15:02:19 [error] 9409#0: *12 open() "/home/writefreely/go/src/code.gyt.is/writefreely/static/css/write.css" failed (13: Permission denied), client: 10.0.2.2, server: _, request: "GET /css/write.css HTTP/1.1", host: "127.0.0.1:9980"

Here port 9980 is where web application runs on. Mentioned file (write.scc) is served by NGINX directly per location directive.

As Linux file permissions were set properly, this suggested SELinux is still kicking in. After a lot of messing around I figured it is easier to generate rules based on errors and warning generated.

First install some utilities:

yum install -y policycoreutils-python

On fresh CentOS 7 machine I could see following messages in the logs:

# cat /var/log/audit/audit.log | grep nginx | grep denied
type=AVC msg=audit(1585828926.295:187): avc:  denied  { read } for  pid=9411 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828926.296:188): avc:  denied  { read } for  pid=9411 comm="nginx" name="h.js" dev="dm-2" ino=3221225570 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828926.418:189): avc:  denied  { read } for  pid=9411 comm="nginx" name="h.js" dev="dm-2" ino=3221225570 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828926.424:190): avc:  denied  { read } for  pid=9411 comm="nginx" name="webfont.js" dev="dm-2" ino=3221225576 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828932.741:191): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828933.608:192): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828934.757:193): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0
type=AVC msg=audit(1585828939.390:194): avc:  denied  { read } for  pid=9409 comm="nginx" name="write.css" dev="dm-2" ino=2150102457 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

Now as those errors are present, run several times following commands:

cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M nginx
semodule -i nginx.pp

This website had good suggestion to run above commands in case 403 forbiddden error still comes up several times to make sure all forbids are captured by audit2allow.

While this example was demonstrated with WriteFreely web application and NGINX server, investigating audit log and allowing denied actions using audit2allow can be quick way to troubleshoot SELinux restrictions instead of turning this system off alltogether (which is a terrible thing to do).

Stay safe!