This is a helper script designed to integrate OpenVPN with the
systemd-resolved
service via DBus instead of trying to override
/etc/resolv.conf
, or manipulate systemd-networkd
configuration files.
Since systemd-229, the systemd-resolved
service has an API available via DBus
which allows directly setting the DNS configuration for a link. This script
makes use of busctl
from systemd to send DBus messages to systemd-resolved
to update the DNS for the link created by OpenVPN.
This script requires:
- Bash 4.3 or above.
- coreutils or
busybox (for the
id
command). - iproute2 (for the
ip
command). - systemd (for the
busctl
andresolvectl
commands).
If available, these will be used for IP address parsing and
validation;1 otherwise update-systemd-resolved
will use native
Bash routines for this.
If available, the logger
command included in the util-linux
distribution
will be used for logging. Otherwise, all logs will go to standard error using
Bash's printf
builtin.
If available, these will be used for serializing the names of the users and
groups allowed to call systemd-resolved
's DBus methods to
JSON lists for use within the generated polkit
rules. Otherwise, update-systemd-resolved
will
fall back to native Bash routines for generating these lists.
If you are using a distribution of Linux with uses the Arch User Repository, the
simplest way to install is by using the openvpn-update-systemd-resolved
AUR package as this will take care of any updates through your package manager.
Debian and
Ubuntu also provide a
.deb
package in their distributions.
Alternatively, the package can be manually installed by running the following:
git clone https://github.com/jonathanio/update-systemd-resolved.git
cd update-systemd-resolved
make
update-systemd-resolved
exposes a Nix flake. You can incorporate this
flake into your flake by adding it to your inputs:
# Your flake.nix
{
inputs = {
# Other inputs here...
update-systemd-resolved.url = "github:jonathanio/update-systemd-resolved";
update-systemd-resolved.inputs.nixpkgs.follows = "nixpkgs"; # optional
};
# Etc.
}
This flake provides the update-systemd-resolved
package for several Linux
architectures. It also provides the update-systemd-resolved
NixOS module:
# Your flake.nix
{
outputs = {nixpkgs, update-systemd-resolved, ...}: {
nixosConfigurations.my-system = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
update-systemd-resolved.nixosModules.update-systemd-resolved
];
};
};
}
Please see the NixOS module documentation for available options.
To view all outputs provided by this flake, run the following command:
$ nix flake show 'github:jonathanio/update-systemd-resolved'
Make sure that you have systemd-resolved
enabled and running. First, make sure
that systemd-resolved.service
is enabled and started:
systemctl enable systemd-resolved.service
systemctl start systemd-resolved.service
Next, you can either configure the system libraries to talk to it using NSS, or
you can override the resolv.conf
file to use systemd-resolved
as a stub
resolver (or both):
Update your /etc/nsswitch.conf
file to look up DNS via the resolve
service
(you may need to install the NSS library which connects libnss to
systemd-resolved
):
# Use /etc/resolv.conf first, then fall back to systemd-resolved
hosts: files dns resolve myhostname
# Use systemd-resolved first, then fall back to /etc/resolv.conf
hosts: files resolve dns myhostname
# Don't use /etc/resolv.conf at all
hosts: files resolve myhostname
The changes will be applied as soon as the file is saved.
Note that some Linux distributions manage /etc/nsswitch.conf
, so
manual edits to /etc/nsswitch.conf
may disappear. Please consult your
distribution's documentation for how to configure /etc/nsswitch.conf
.
If you run the OpenVPN client as an unprivileged user, you may need to add
polkit rules authorizing that user to perform the various DBus calls that
update-systemd-resolved
makes. Some installation methods bundle these rules;
for instance, on Arch Linux, where openvpn-client@<name>.service
instances
run as the unprivileged openvpn
user, the
openvpn-update-systemd-resolved AUR package ships suitable rules in the
file /etc/polkit-1/rules.d/10-update-systemd-resolved.rules
.
Warning
update-systemd-resolved
strives to generate polkit rules with the smallest
scope consistent with its proper functioning. Nonetheless, in order to avoid
security risks, you are encouraged to review the generated polkit rules
before installing them.
You can also generate suitable rules with (some variation on) the following commands:
$ update-systemd-resolved print-polkit-rules --polkit-allowed-user some-user --polkit-allowed-user another-user > ./10-custom-update-systemd-resolved.rules
$ sudo install -Dm0640 ./10-custom-update-systemd-resolved.rules /etc/polkit-1/rules.d/10-custom-update-systemd-resolved.rules
This will allow update-systemd-resolved
to successfully make its DBus calls
when invoked from OpenVPN client services that run as the users some-user
or
another-user
.
You can also authorize members of specified groups with:
$ update-systemd-resolved print-polkit-rules --polkit-allowed-group some-group --polkit-allowed-group another-group > ./10-custom-update-systemd-resolved.rules
$ sudo install -Dm0640 ./10-custom-update-systemd-resolved.rules /etc/polkit-1/rules.d/10-custom-update-systemd-resolved.rules
This will allow update-systemd-resolved
to successfully make its DBus calls
when invoked from OpenVPN client services that run under the groups
some-group
or another-group
.
Finally, you can generate rules that pull appropriate user and group values from OpenVPN systemd units with:
$ update-systemd-resolved print-polkit-rules --polkit-systemd-openvpn-unit my-openvpn-client.service
$ sudo install -Dm0640 ./10-custom-update-systemd-resolved.rules /etc/polkit-1/rules.d/10-custom-update-systemd-resolved.rules
Given:
$ systemctl show -P User my-openvpn-client.service
myuser
$ systemctl show -P Group my-openvpn-client.service
mygroup
The generated 10-custom-update-systemd-resolved.rules
file will contain rules
allowing the myuser
user and members of the mygroup
group to perform the
requisite DBus calls.
You can run update-systemd-resolved print-polkit-rules
with any combination
of --polkit-allowed-user
, --polkit-allowed-group
, and
--polkit-systemd-openvpn-unit
. If called without options,
update-systemd-resolved print-polkit-rules
will attempt to derive appropriate
user and group authorizations from a systemd OpenVPN unit matching
openvpn-client@.service
, the systemd service
template
used for OpenVPN client services on distributions including Arch Linux.
The systemd-resolved
service (since systemd-231) also listens on 127.0.0.53
via the lo
interface, providing a stub resolver which any client can call to
request DNS, whether or not it uses the system libraries to resolve DNS, and
you no longer have to worry about trying to manage your /etc/resolv.conf
file. This set up can be installed by linking to stub-resolv.conf
:
ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
Finally, update your OpenVPN configuration file and set the up
and down
options to point to the script, and down-pre
to ensure that the script is run
before the device is closed:
script-security 2
up /usr/local/libexec/openvpn/update-systemd-resolved
up-restart
down /usr/local/libexec/openvpn/update-systemd-resolved
down-pre
# If needed, to permit `update-systemd-resolved` to find utilities it depends
# on. Adjust to suit your system.
#setenv PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
It is recommended to use up-restart
in your configuration to ensure that
upate-systemd-resolved
is run on restarts - where the connection is
re-established but the TUN/TAP device remained open (for example, where the
original connection has timed out and persist-tun
is enabled). If you do not
have persist-tun
set, or you use ping-exit
instead of ping-timeout
, you
most likely will not need this.
The down
and down-pre
options here may not work as expected where the
openvpn
daemon drops privileges after establishing the connection (i.e. when
using the user
and group
options). This is because, by default, only the
root
user will have the privileges required to talk to
systemd-resolved.service
over DBus. The openvpn-plugin-down-root.so
plug-in
does provide support for enabling the down
script to be run as the root
user, but this has been known to be unreliable.
You can authorize unprivileged users or groups to revert the OpenVPN link's DNS settings during the "down" phase using the methods described in the "Polkit Rules" section.
Ultimately, dropping privileges shouldn't affect normal "down" operation, since
systemd-resolved.service
will remove all settings associated with the link
(and therefore naturally update /etc/resolv.conf
, if you have it symlinked)
when the TUN or TAP device is closed. The option for down
and down-pre
just
make this step explicit before the device is torn down rather than implicit on
the change in environment.
Alternatively if you don't want to edit your client configuration, you can add
the following options to your openvpn
command:
openvpn \
--script-security 2 \
--setenv PATH '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \
--up /usr/local/libexec/openvpn/update-systemd-resolved --up-restart \
--down /usr/local/libexec/openvpn/update-systemd-resolved --down-pre
Tip
The --setenv PATH
option shown above is intended to allow
update-systemd-resolved
to find its prerequisites.
Depending on your system's configuration, you may not need --setenv PATH
,
or you may need to specify a different PATH
value than the one shown above.
Or, you can add the following argument to the command-line arguments of
openvpn
, which will use the update-systemd-resolve.conf
file instead:
openvpn --config /usr/local/share/doc/openvpn/update-systemd-resolved.conf
Note
The path to update-systemd-resolved.conf
may differ depending on how you
installed update-systemd-resolved
. Additionally, both the file's path and
its contents are subject to change in future releases. Rather than using the
example configuration file directory, you may want to copy the file to
another location and then run openvpn --config <other-location>/update-systemd-resolved.conf
.
update-systemd-resolved
works by processing the dhcp-option
commands set in
OpenVPN, either through the server, or the client, configuration. Note
that there are no local or system options to be configured. All configuration
for this script is handled through OpenVPN, including, for example, the name of
the interface to be configured.
Setting DNS servers
0.0.0.0
0.0.0.0:5353
0.0.0.0#my.resolver.net
0.0.0.0:5353#my.resolver.net
::1
[::1]:5353
::1#my.resolver.net
[::1]:5353#my.resolver.net
This sets the DNS servers for the link and can take any IPv4 or IPv6 address.
Setting IPv6-only DNS servers
::1
[::1]:5353
::1#my.resolver.net
[::1]:5353#my.resolver.net
This sets the DNS servers for the link and can take only IPv6 addresses.
Setting the primary domain
example.com
The primary domain for this host. If set multiple times, the first provided is
used as the primary search domain for bare hostnames. Any subsequent DOMAIN
options will be added as the equivalent of DOMAIN-SEARCH
options. All
requests for this domain as well will be routed to the DNS
servers provided
on this link.
Setting secondary domains
example.com
Secondary domains which will be used to search for bare hostnames (after any
DOMAIN
, if set) and in the order provided. All requests for this domain will
be routed to the DNS
servers provided on this link.
Routing DNS queries
example.com
All requests for these domains will be routed to the DNS
servers provided on
this link. They will not be used to search for bare hostnames, only routed. A
DOMAIN-ROUTE
option for .
(single period) will instruct systemd-resolved
to route the entire DNS name-space through to the DNS
servers configured for
this connection (unless a more specific route has been offered by another
connection for a selected name/name-space). This is useful if you wish to
prevent DNS leakage.
Enabling DNSSEC
yes
,true
no
,false
default
allow-downgrade
Control of DNSSEC should be enabled (yes
, true
) or disabled (no
,
false
), or allow-downgrade
to switch off DNSSEC only if the server doesn't
support it, for any queries over this link only, or use the system default
(default
).
Flushing DNS caches
yes
,true
no
,false
Whether or not to flush all local DNS caches. Enabled by default.
Resetting learnt DNS server feature levels
yes
,true
no
,false
Whether or not to forget learnt DNS server feature levels.
Resetting resolver statistics
yes
,true
no
,false
Whether or not to reset resolver statistics.
Default DNS query routing
yes
,true
no
,false
If true, this link's configured DNS servers are used for resolving domain names
that do not match any link's configured Domains=
setting. If false, this
link's configured DNS servers are never used for such domains, and are
exclusively used for resolving names that match at least one of the domains
configured on this link.
Enabling DNS-over-TLS
yes
,true
no
,false
•opportunistic
•default
If true all connections to the server will be encrypted. Note that this mode
requires a DNS server that supports DNS-over-TLS and has a valid certificate.
If the hostname was specified in DNS=
by using the format
address#server_name
it is used to validate its certificate and also to enable
Server Name Indication (SNI) when opening a TLS connection. Otherwise the
certificate is checked against the server's IP. If the DNS server does not
support DNS-over-TLS all DNS requests will fail. When set to opportunistic
DNS request are attempted to send encrypted with DNS-over-TLS. If the DNS
server does not support TLS, DNS-over-TLS is disabled. Note that this mode
makes DNS-over-TLS vulnerable to "downgrade" attacks, where an attacker might
be able to trigger a downgrade to non-encrypted mode by synthesizing a response
that suggests DNS-over-TLS was not supported. If set to false, DNS lookups are
send over UDP. If set to default
, uses the system default.
Enabling Link-Local Multicast Name Resolution
yes
,true
no
,false
•resolve
•default
When true, enables Link-Local Multicast Name Resolution on the link. When set
to resolve
, only resolution is enabled, but not host registration and
announcement. If set to default
, uses the system default.
Enabling Multicast DNS
yes
,true
no
,false
•resolve
•default
When true, enables Multicast DNS support on the link. When set to resolve
,
only resolution is enabled, but not host or service registration and
announcement. If set to default
, uses the system default.
Configuring DNSSEC Negative Trust Anchors
trusted.org
If specified and DNSSEC is enabled, look-ups done via the interface's DNS
server will be subject to the list of negative trust anchors, and not require
authentication for the specified domains, or anything below it. Use this to
disable DNSSEC authentication for specific private domains, that cannot be
proven valid using the Internet DNS hierarchy. By default,
update-systemd-resolved
does not set any negative trust anchors.
push "dhcp-option DNS 10.62.3.2"
push "dhcp-option DNS 10.62.3.3"
push "dhcp-option DNS6 2001:db8::a3:c15c:b56e:619a"
push "dhcp-option DNS6 2001:db8::a3:ffec:f61c:2e06"
push "dhcp-option DOMAIN example.office"
push "dhcp-option DOMAIN example.lan"
push "dhcp-option DOMAIN-SEARCH example.com"
push "dhcp-option DOMAIN-ROUTE example.net"
push "dhcp-option DOMAIN-ROUTE example.org"
push "dhcp-option DNSSEC yes"
This, added to the OpenVPN server's configuration file will set two IPv4 DNS
servers and two IPv6 and will set the primary domain for the link to be
example.office
. Therefore if you try to look up the bare address mail
then
mail.example.office
will be attempted first. The domains example.lan
and
example.com
are also added as an additional search domain, so if
mail.example.office
fails, then mail.example.lan
will be tried next,
followed by mail.example.com
.
Requests for example.net
and example.org
will also be routed through to the
four DNS servers listed, but they will not be appended (i.e.
mail.example.net
will not be attempted, nor mail.example.org
, if
mail.example.office
or mail.example.com
do not exist).
Finally, DNSSEC has been enabled for this link (and this link only).
Important
Required reading: systemd-resolved.service
and VPNs. This
document includes, among other things, an overview of search domains, routing
domains, and systemd-resolved
's default-route
boolean settings.
Understanding these concepts will help you configure your local
systemd-resolved
instance to ensure that DNS queries go where you want them
to go.
DNS Leakage is something to be careful of when using any VPN or untrusted network, and it can heavily depend on how you configure your normal DNS settings as well as how you configure the DNS on your VPN connection.
By default, systemd-resolved
will send all DNS queries to at least one
DNS server on every link configured with DNS servers. The first to reply
back with a valid query is the one returned to the client, and the last to
return back a failure (assuming all other queries also failed) will also be
returned to the client.
The changes in this handling come in when you start using the DOMAIN
,
DOMAIN-SEARCH
and DOMAIN-ROUTE
options. The three differ in how domains
are treated for searching bare domains, but all three work exactly the same
when it comes to how it routes domains to specific DNS servers.
Any domain added using DOMAIN
, DOMAIN-SEARCH
, or DOMAIN-ROUTE
will be
added explicitly to the VPN link and therefore any queries for domain suffixes
which match these will be routed through this link, and only this link. Any
other domains which do not match these will revert back to distributing the
queries across all links.
There are two ways to override this:
If you want to prevent DNS queries leaking over untrusted networks (for
example, over public WiFi hotspots), then you need to tell systemd-resolved
to send all DNS queries over the VPN link. To do this, add the following to
your server or client VPN configurations respectively:
# Server Configuration
push "dhcp-option DOMAIN-ROUTE ."
# Client Configuration
dhcp-option DOMAIN-ROUTE .
All DNS queries (which do not match a more explicit entry on another link) will now be routed over the VPN only.
In an alternate situation, you may want to have DNS queries specifically routed over the VPN for corporate or private network access, but you don't want your general DNS queries to be visible to anyone who has access to the logs of the corporate DNS servers.
This option cannot be directly managed by update-systemd-resolved
as you need
to configure the network settings of other links to send all queries by default
to your nominated DNS server (e.g. over ens0
or wlp2s0
for your Ethernet or
Wireless network cards). This needs to be configured under the [Network]
section of your .network
file for your interface in /etc/systemd/network
.
For example:
[Network]
DHCP=yes
DNS=8.8.8.8
DNS=8.8.4.4
Domains=.
When you connect, all domains except those explicitly listed using the DOMAIN
,
DOMAIN-SEARCH
, or DOMAIN-ROUTE
options of your VPN link will be sent to the
DNS server of your nominated link.
Note that these two options are mutually exclusive, as if you establish a VPN
link with DOMAIN-ROUTE
set to .
while you have also configured it inside a
.network
file via systemd-networkd
, then you will have two links
responsible for routing all queries, and so both links will get all requests.
How to manage the DNS settings of other links while the VPN is operational is outside the scope of this script at this time.
There are a number of known issues relating to some third-party servers and services:
This script may not be compatible with certain versions of NetworkManager. It
seems that NetworkManager overrides the up
command to use its own helper
script (nm-openvpn-service-openvpn-helper). The script that ships
with NetworkManager only supports DNS
and DOMAIN
options (not DNS6
,
DOMAIN-SEARCH
and DOMAIN-ROUTE
, nor DNSSEC
overrides). It may also be
liable to set the other network interfaces to route ~.
DNS queries (i.e the
whole name-space) to the LAN or ISP DNS servers, making it difficult to
override using DOMAIN
- see the DNS leakage section.
There is a regression with versions of NetworkManager 1.2.6 through 1.26.4 (see
LP#1671606 and LP#1688018) which means that it will
automatically set all normal network interfaces with ~.
for DNS routing.
This means that even if you set dhcp-option DOMAIN-ROUTE .
for your VPN
connection, you will still leak DNS queries over potentially insecure networks.
If you are concerned by potentially leaking DNS on systems which use NetworkManager, you may need to configure an additional script into NetworkManager which change the domain routing settings on all non-VPN interfaces.
This issue was fixed in NetworkManager version 1.26.6; now,
NetworkManager only enables the DefaultRoute
option on managed interfaces.
$ resolvectl query eu-central-1.console.aws.amazon.com
eu-central-1.console.aws.amazon.com: resolve call failed: DNSSEC validation failed: no-signature
# or
$ resolvectl query eu-central-1.console.aws.amazon.com
eu-central-1.console.aws.amazon.com: resolve call failed: DNSSEC validation failed: incompatible-server
If you are seeing failed queries in your logs due to DNSSEC issues, support may be
partially or fully enabled and you are now working with a server which does not
support this extension. You may therefore need to set DNSSEC
to no
(or
maybe just allow-downgrade
) in your VPN configuration.
dhcp-option DNSSEC allow-downgrade
The NSS interface for systemd-resolved
may be deprecated and has already been
flagged for deprecation in Ubuntu (see LP#1685045 for details). In
this case, you should use the Stub Resolver method now.
Fedora 28 makes use of authselect
to manage the NSS settings on the system.
Directly editing nsswitch.conf
is not recommended as it may be overwritten at
any time if authselect
is run. Proper overrides may not yet be possible - see
the authselect project repository for details. However, like
Ubuntu, the Stub Resolver method is recommended here too.
Note that Fedora 33 enables systemd-resolved
by default and configures
/etc/nsswitch.conf
to use the systemd-resolved
NSS interface; see the
Fedora changelog entry for details.
If you can help with any of these areas, or have bug fixes, please fork and raise a Pull Request for me.
I have built a basic test framework around the script which can be used to
monitor and validate the calls made by the script based on the environment
variables available to it at run-time. Please add a test for any new features
you may wish to add, or update any which are wrong, and test your code by
running ./run-tests
from the root of the repository. There are no dependencies
on run-tests
- it runs 100% bash and doesn't call out to any other program or
language.
GitHub Actions are enabled on this repository: Click the link at the top of this README to see the current state of the code and its tests.
Please see HACKING.md
for notes on developing
update-systemd-resolved
.
GPL
Jonathan Wright jon@than.io
Footnotes
-
Required for translating numerical labels like
1.2.3.4
to the byte arrays recognized by theSetLinkDNS()
function onsystemd-resolved
'sorg.freedesktop.resolve1.Manager
D-Bus interface). ↩