SELinux is preventing python from name_connect access on the tcp_socket port 443

The problem

I am trying to set up SMART monitoring for my disks on my Fedora Server 36 installation. I have installed smartmontools and have the following configuration in /etc/smartmontools/smartd.conf:

DEFAULT -a -m <nomailer> -M exec /usr/local/bin/smartd_alert.py -M test

/dev/nvme0
/dev/disk/by-partlabel/data1
/dev/disk/by-partlabel/data2
/dev/disk/by-partlabel/parity1

I am using the -m exec directive to run an alert script instead of using the mailer functionality. The Python script sends the notification to me via a 3rd party service (Pushbullet).

This has worked for me on previous Fedora releases. However, with Fedora 36, the Python script is failing with name resolution after starting the smartd service:

socket.gaierror: [Errno -3] Temporary failure in name resolution

This does not happen if I run the script as-is from the shell, only if it is run via the smartd service.

Investigation

I googled for a bit and found out about a SELinux troubleshooting tool, so I installed it and ran it:

sudo dnf install setroubleshoot-server
sudo sealert -a /var/log/audit/audit.log

This prints the following output:

100% done
found 1 alerts in /var/log/audit/audit.log
--------------------------------------------------------------------------------

SELinux is preventing python from name_connect access on the tcp_socket port 443.

*****  Plugin catchall_boolean (89.3 confidence) suggests   ******************

If you want to allow nis to enabled
Then you must tell SELinux about this by enabling the 'nis_enabled' boolean.

Do
setsebool -P nis_enabled 1

*****  Plugin catchall (11.6 confidence) suggests   **************************

If you believe that python should be allowed name_connect access on the port 443 tcp_socket 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 'python' --raw | audit2allow -M my-python
# semodule -X 300 -i my-python.pp


Additional Information:
Source Context                system_u:system_r:fsdaemon_t:s0
Target Context                system_u:object_r:http_port_t:s0
Target Objects                port 443 [ tcp_socket ]
Source                        python
Source Path                   python
Port                          443
Host                          <Unknown>
Source RPM Packages           
Target RPM Packages           
SELinux Policy RPM            selinux-policy-targeted-36.10-1.fc36.noarch
Local Policy RPM              smartmontools-selinux-7.3-2.fc36.noarch
Selinux Enabled               True
Policy Type                   targeted
Enforcing Mode                Enforcing
Host Name                     lunatea
Platform                      Linux lunatea 5.18.5-200.fc36.x86_64 #1 SMP
                              PREEMPT_DYNAMIC Thu Jun 16 14:51:11 UTC 2022
                              x86_64 x86_64
Alert Count                   56
First Seen                    2022-07-29 21:06:22 EEST
Last Seen                     2022-07-29 22:01:34 EEST
Local ID                      0addaf3a-f2b0-4f33-bf01-cf954ba10495

Raw Audit Messages
type=AVC msg=audit(1659121294.93:468): avc:  denied  { name_connect } for  pid=1770 comm="python" dest=443 scontext=system_u:system_r:fsdaemon_t:s0 tcontext=system_u:object_r:http_port_t:s0 tclass=tcp_socket permissive=0


Hash: python,fsdaemon_t,http_port_t,tcp_socket,name_connect

So the root of the issue seems to be SELinux limiting Python’s networking capabilities, for whatever reason.

The questions

I have very little knowledge of SELinux, so I am in need of guidance here.

First, the sealert tool seems to suggest that I should enable the nis_enabled boolean. I have no idea what the security implications of this are, so I am not comfortable running that command blindly. I do not want any unintended side effects. All I know is that I absolutely expect a Python script to be able to make network requests regardless of whether I run it via the shell or a systemd service. I do not necessarily want to make changes to any other default security rules set in Fedora by default.

Secondly, the tool also suggests – with less confidence – that this may actually be a bug. I don’t think I have enough knowledge to conclude whether this is intended behaviour or not. But it does seem weird to me that I cannot make network requests in my Python programs. The tool also offers a method to generate a local policy to allow the access. I do not know if this is a better/safer method than the nis_enabled thing.

So the questions are:

  1. Am I doing something wrong here?
  2. Should I report this as a bug on BugZilla?
  3. Can the nis_enabled boolean be safely enabled to allow Python scripts to make network requests, but without negatively affecting the security of the system?
  4. Instead of the nis_enabled method, would it be better to generate a local policy specifically to enable Python network access (and nothing else)?

I would definitely file a bug on bugzilla.redhat.com since you are trying something that is one-off from the normal.

Let the developers either fix it or tell you the risks involved and why that is not allowed. You then make the decision once you are aware of the specific risks.

In the meantime you may be able to use one of the suggested fixes and get things to work as you wish.

1 Like

I doubt the devs that monitor RH’s bugzilla would give much consideration to the selinux alerts that an end user’s script is generating. This forum is probably the better place for that sort of request for help.

I’m no selinux expert myself, but for the most part, I think it is all about the “contexts”. Certain contexts are allowed to do certain things. In this case, the “fsdaemon_t” (file system daemon type) is trying to open port 443 which is in the “http_port_t” context. I don’t know if the nis_enabled boolean would allow that sort of thing, but that is probably a “bigger hammer” to use than necessary. Generating a local policy should be the more surgical option. The local policy would likely just add a rule to allow “file system daemons” to “transition” to “http_port_t” contexts and open 443. It might still be a little broad in that the generated local policy might match all “file system daemons” on your local system. But it would still be more restrictive than the nis_enabled boolean. A real selinux guru might be able to further edit the generated policy to make it even more restrictive and/or change the script’s label to a better context. But I’m not that talented of a selinux guru.

Edit: I think the following link is probably the best answer to your original question.

Edit: I think one thing that was omitted from the above blog post is that the script would need to be labeled with the new selinux type (e.g. “chcon -t myapp_exec_t /usr/local/bin/smartd_alert.py”; use “ls -Z /usr/local/bin/smartd_alert.py” to view the label on the script)

Edit: In this case, you might also need to set the “role” label to match as well – “chcon -r system_r /usr/local/bin/smartd_alert.py” (I’m not sure).

2 Likes

Thank you both for the input and information. I think I will first file a bug on Bugzilla and see what the devs have to say about it. Then I guess I will try to narrow down a fitting policy for the use case based on the details Gregory posted.

The nis_enabled boolean definitely does not seem like the way to go:

sesearch -b nis_enabled -A | wc -l

This returns 776 other rules that would be affected by enabling the bit.

Edit: here’s the bug report.

Option 4 is what I would chose. First you would need to run in permissiove mode, as there probably will be other selinux issues after the first one.

it is actually the permission for the smartd process and all its child processes including your python program.

The smartd program runs in the selinux context
fsdaemon_t and that gives it certain permissions and restrictions. Your python program then needs permissions to access something in the :http_port_t context i.e. the port 443. If your program were to continue it probably would run into other restrictions. Therefore: permissive mode to cach those.

SElinux has a lot of issues like that and the Bogzilla page is quite busy,. The maintainer really tries to fix those, but his time is of course limmited.

1 Like

I tried writing a policy according to the details given by Gregory, and it would seem like I got it working.

Just to sum it up, I created a policy file smartd_subprocess_exec.te that looks like this:

policy_module(smartd_exec_subprocess, 1.0)

gen_require(`
  type fsdaemon_t;
  type unconfined_t;
  role system_r;
')

# define a type for smartd-exec files
type smartd_exec_t;
files_type(smartd_exec_t)

# when a process labeled with the 'fsdaemon_t' type (e.g. smartd)
# executes a file labeled with the 'smartd_exec_t' type, transition
# the spawned process into the 'unconfined_t' domain
domain_auto_trans(fsdaemon_t, smartd_exec_t, unconfined_t);

# set 'smartd_exec_t' as en entrypoint to the 'unconfined_t' domain
allow unconfined_t smartd_exec_t:file entrypoint;

# smartd is running as role 'system_r'; allow 'unconfined_t' to be executed in this role
role system_r types unconfined_t;

I installed the SELinux development files, built and installed the module, and changed the context for the script:

sudo dnf install selinux-policy-devel -y
make -f /usr/share/selinux/devel/Makefile smartd_exec_subprocess.pp
sudo semodule -i smartd_exec_subprocess.pp
sudo chcon -t smartd_exec_t /usr/local/bin/smartd_alert.py

Finally, I restarted the smartd service, and the script now succeeds (I got the notification):

Jul 31 20:27:37 lunatea smartd[1828]: Monitoring 3 ATA/SATA, 0 SCSI/SAS and 1 NVMe devices
Jul 31 20:27:37 lunatea smartd[1828]: Executing test of /usr/local/bin/smartd_alert.py to <nomailer> ...
Jul 31 20:27:38 lunatea smartd[1828]: Test of /usr/local/bin/smartd_alert.py to <nomailer>: successful

Thanks again, @glb!

While it is probably not the perfect solution (seems like transitioning to unconfined_t is somewhat frowned upon?), I think I am fine with this for now, since the unconfinement should be limited to just my specific script while other subprocesses of smartd (or other software in the fsdaemon_t context) are unaffected.

P.S. I noticed that Python scripts are unconfined anyway when they are run directly from the shell. ¯\_(ツ)_/¯

1 Like

Exactly. I think this solution might actually be more secure than what you would get with the autogenerated rules since there is little chance of a bug or exploit actively looking for your custom script with your custom type label. Whereas the autogenerated rule would probably just open port 443 to anything running as fsdaemon_t on your system. The latter could easily be exploited by some hacker slipping something like, e.g., curl hacker.example.net | bash into an upstream package (well, hopefully nothing that obvious would get through, but you get the idea).

Glad you got it figured out and thanks for letting us know the details about how to get it working. :slightly_smiling_face:

2 Likes

The assignee has now changed the resolution to NOTABUG, i.e. the restriction is intended behaviour in Fedora 36. The developer also suggested the following:

$ cat local_fsdaemon_http.cil
(allow fsdaemon_t http_port_t (tcp_socket (name_connect)))
$ semodule -i local_fsdaemon_http.cil

But again, this will grant the permission to any process under the fsdaemon_t domain.