Bug 2112586

Summary: SELinux is preventing Python scripts launched via smartd’s exec directive from making network requests
Product: [Fedora] Fedora Reporter: andoryuuhonmono
Component: selinux-policyAssignee: Zdenek Pytela <zpytela>
Status: CLOSED NOTABUG QA Contact: Fedora Extras Quality Assurance <extras-qa>
Severity: medium Docs Contact:
Priority: medium    
Version: 36CC: dwalsh, grepl.miroslav, lvrabec, mmalik, omosnace, pkoncity, vmojzis, zpytela
Target Milestone: ---Keywords: Triaged
Target Release: ---   
Hardware: x86_64   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: 2022-09-12 13:26:22 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description andoryuuhonmono 2022-07-30 20:07:27 UTC
Description of problem:

I am trying to set up SMART monitoring for 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 over the internet.

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 error does **not** happen if I run the script as-is from the shell, only if it is run via the smartd service.

After running `sudo sealert -a /var/log/audit/audit.log`, I get 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, SELinux is limiting the networking functionality of the Python script. Now, I have very little knowledge on SELinux myself, but I would expect my Python scripts to be able to make network requests regardless of if they are run directly via a shell or via a systemd service. I think the `exec` directive of smartd is pretty useless if the script given as an argument does not have networking capabilities.


Version-Release number of selected component (if applicable): 36.10


How reproducible: always


Steps to Reproduce:
1. Have a Python script that makes a network request, this can be simply something like:
```
#!/usr/bin/env python
from urllib import request
request.urlopen('https://example.com')
```

2. Configure `/etc/smartmontools/smartd.conf` to run the script via the `exec` directive (make sure to use the `-M test` option as well so that a test run will be made when the daemon starts)

3. Start the service: `systemctl restart smartd`

4. Observe the log via `systemctl status smartd` or `journalctl -eu smartd`


Actual results:

The alert script always fails to make a network request.


Expected results:

The alert script should be able to make a network request.


Additional info:

I am not comfortable enabling the `nis_enabled` boolean as suggested by the sealert tool, since the boolean seems to affects hundreds of other rules as well, and I do not want to negatively affect the security of my system. All I know is that I expect my Python scripts to be able to reach the internet.

Since I do not have enough knowledge on the area, I do not actually know whether the networking restrictions are intended behaviour for smartd scripts, so I hope a developer could chime in and tell if this should be happening or not.

If this is indeed intended behaviour, I would also very much appreciate any guidance on what would be the ideal solution for configuring SELinux to allow the behaviour I’m expecting from smartd (and *not* affecting anything else in the process). I do not know if the other suggestion provided by sealert (the audit2allow and semodule commands) is restrictive enough. I have no previous experience with configuring anything related to SELinux.

Comment 1 andoryuuhonmono 2022-08-01 07:53:55 UTC
I found some examples, so I have now written the following policy to work around this issue:


```
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;
```


This allows my specific script to run unconfined so that the network requests will work.

Comment 2 Zdenek Pytela 2022-09-12 13:26:22 UTC
As this is a local customization, I also suggest use a local SELinux policy module like this:

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