Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resolved: DNS access control and DNS controlled firewalling #17053

Open
topimiettinen opened this issue Sep 14, 2020 · 5 comments
Open

resolved: DNS access control and DNS controlled firewalling #17053

topimiettinen opened this issue Sep 14, 2020 · 5 comments
Labels
needs-discussion 🤔 resolve RFE 🎁 Request for Enhancement, i.e. a feature request selinux

Comments

@topimiettinen
Copy link
Contributor

Is your feature request related to a problem? Please describe.
It's difficult to make useful firewalls based on filtering IP addresses since the internet operates on DNS domain names but the firewalls actually operate on IP addresses. In simple, static cases it could be possible to resolve the DNS addresses only once at resolver startup (boot etc), but not in general. Especially statically resolving domain names for address pools (e.g. pool.ntp.org) at resolver startup would not work as the DNS servers may give a different address each time the name is resolved.

Describe the solution you'd like
There are two parts in this idea.

  1. Introduce access control for DNS requests for systemd-resolved, i.e. resolve only domains approved for the requesting service. For example, systemd-timesyncd.service could be allowed to resolve only *.pool.ntp.org, but not facebook.com (infected service trying to check instructions from botnet C2).
  2. Control firewall tables based on the DNS requests received by systemd-resolved. As an example for NTP, initially deny all outgoing NTP port access. After a request from systemd-timesyncd.service for pool.ntp.org has been resolved to an IP address (but before the DNS reply is given back to caller), update the firewall to allow access for NTP for this IP address. The firewall rules would be removed automatically later based on selected conditions.

Typical firewalls only enforce global rules for the whole system but with SELinux, it's possible to create firewall rules which use SELinux labels (SELinux networking support). Thus optionally systemd-resolved could let SELinux label ntpd_t (systemd-timesyncd.service) to access NTP ports of pool.ntp.org but deny NTP to other services, or allow APT (Debian package tool) to access debian.org but not botnet.org with HTTPS.

For 1., systemd-resolved would need to positively identify the service and for SELinux option of 2., the label of the process making the request (with SO_PEERSEC).

The removal logic probably needs to be selected per service. For some services which resolve the server address only once, or always resolve the address before starting the connection, it would be enough to purge previous firewall entries once there's a new request. Some others may look up several host names and use all of them, so the logic should be to keep the addresses, maybe with configurable timeout.

The benefit would be more secure services and much tighter firewalls. 1. would implement simple DNS based access control for services. It would not block accesses with hard coded IP values. 2. would tighten 1. so that even known IP addresses would be blocked, unless a DNS request (subject to rules of 1.) was made in advance.

Part 1. would be already useful without 2.

@topimiettinen topimiettinen added needs-discussion 🤔 resolve RFE 🎁 Request for Enhancement, i.e. a feature request selinux labels Sep 14, 2020
@topimiettinen
Copy link
Contributor Author

This could be configured with a simple tabular file and drop-ins like:

Origin IF Service MAC label Request Target Action Firewall Cleanup
* * * * * * Deny no no
* * * * AAAA * Deny no no
varlink * systemd-timesyncd.service selinux=system_u:system_r:ntpd_t A pool.ntp.org Allow udp=123 max-entries=4
udp lo session-*.scope selinux=user_u:user_r:ssh_t A *.github.com Allow tcp=22 timeout=3600s
* * session-*.scope selinux=user_u:user_r:chromium_t * *.example.com Deny tcp=443 no
udp lo session-*.scope selinux=user_u:user_r:chromium_t * * Allow tcp=443 timeout=86400s
udp lo session-*.scope selinux=root:sysadm_r:dpkg_t:s0-s0:c0.c1023 * *.debian.org Allow tcp=443 timeout=600s

"Origin" means the source of the DNS request (D-Bus, varlink, UDP). The first line sets up allow-listing but disables firewall control. The second drops requests for IPv6 addresses.

@topimiettinen
Copy link
Contributor Author

Sadly I can't find a way to get the SELinux context for the process that sent the UDP DNS packet. I've looked at the following options:

  • setsockopt(, SOL_SOCKET, SO_PASSSEC,,) only works for Unix sockets.
  • setsockopt(, SOL_SOCKET, SO_PEERCRED,,) only works for Unix sockets.
  • setsockopt(, SOL_SOCKET, SO_PEERSEC,,) only works for stream sockets (TCP). Probably it has the same "feature" as IP_PASSSEC below. libselinux getpeercon() uses SO_PEERSEC internally, so there are the same limitations.
  • setsockopt(, SOL_IP, IP_PASSSEC,,) works for UDP sockets. However, the label returned is not from the process context (say user_u:user_r:user_t:s0) but NetLabel peer class, for example I got system_u:object_r:loopback_peer_t:s0 since I'm not using Labeled IPSec. Probably with Labeled IPSec the label of the process would be returned.

A feature which only works for those who have 1) SELinux and 2) SELinux Networking enabled and 3) Labeled IPSec correctly configured, would probably be too obscure. However, there are still other MAC systems and it's also possible to use more than one concurrently. Does IP_PASSSEC work for AppArmor or Smack?

@topimiettinen
Copy link
Contributor Author

And, I just realized that since we can't get the process ID with SO_PEERCRED, it's probably not possible to find out the name of the service either. Without any kind of caller ID, this feature would only work at whole system level which probably is not very interesting. 😞

@topimiettinen
Copy link
Contributor Author

Luckily when using nss-resolve, the connection is made via D-Bus. Then it's possible to find out the SELinux context of the calling process with sd_bus_query_sender_creds() and then sd_bus_creds_get_selinux_context(). For some reason, I can't get the unit (or slice) name with sd_bus_creds_get_unit() (or sd_bus_creds_get_slice()).

I think requiring nss-resolve is not so big limitation.

@topimiettinen
Copy link
Contributor Author

#17126 implements the first part of this. With only bus access possible, origin and interface restrictions do not make sense. I also dropped the request type (A/AAAA etc) for simplicity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-discussion 🤔 resolve RFE 🎁 Request for Enhancement, i.e. a feature request selinux
Development

No branches or pull requests

1 participant