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

Upgrade through Dashboard fails if AGH service is run as non-root user #1193

Closed
kinggrowler opened this issue Nov 22, 2019 · 24 comments
Closed

Comments

@kinggrowler
Copy link

kinggrowler commented Nov 22, 2019

When an update for AdGuard Home is available, the dashboard displays a notice about the available upgrade along with an "Upgrade now" button.

Clicking the button does nearly everything correctly: the old binary is archived, the new one downloaded, the systemd service is restarted.

However, when the service is run as a non-root user, the "setcap" command must be manually executed again.

sudo setcap CAP_NET_BIND_SERVICE=+eip /opt/AdGuardHome/AdGuardHome

My question: is this expected behaviour, and if so, is there a better way I can handle this than manually logging in and running the command?

  • Version of AdGuard Home server:
    • 0.992 to 0.99.3
  • How did you setup DNS configuration:
    • AdGuard Home is configured as my network's nameserver through my router DHCP configuration.
  • Operating system and version:
    • Various Ubuntu and Debian-based LXC containers where AGH service is run as a non-root user.

Expected Behavior

Ideally, AGH would handle setting of the CAP_NET_BIND_SERVICE capability. Baring that, AGH should display a warning message before upgrading via Dashboard.

Actual Behavior

AGH fails to restart after upgrade due to inability to bind to a privileged port.

Screenshots

● AdGuardHome.service - AdGuard Home: Network-level blocker
   Loaded: loaded (/etc/systemd/system/AdGuardHome.service; enabled; vendor preset: enabled)
   Active: activating (auto-restart) (Result: exit-code) since Fri 2019-11-22 10:40:49 PST; 4s ago
  Process: 1068 ExecStart=/opt/AdGuardHome/AdGuardHome -s run (code=exited, status=1/FAILURE)
 Main PID: 1068 (code=exited, status=1/FAILURE)

Nov 22 10:40:49 adguard systemd[1]: AdGuardHome.service: Failed with result 'exit-code'.

aghome@adguard:~$ cd /opt/AdGuardHome/
aghome@adguard:~$ sudo setcap CAP_NET_BIND_SERVICE=+eip /opt/AdGuardHome/AdGuardHome

aghome@adguard:/opt/AdGuardHome$ sudo systemctl restart AdGuardHome.service
aghome@adguard:/opt/AdGuardHome$ sudo systemctl status AdGuardHome.service
● AdGuardHome.service - AdGuard Home: Network-level blocker
   Loaded: loaded (/etc/systemd/system/AdGuardHome.service; enabled; vendor preset: enabled)
   Active: active (running) since Fri 2019-11-22 10:43:01 PST; 4s ago
 Main PID: 1280 (AdGuardHome)
    Tasks: 13 (limit: 4915)
   CGroup: /system.slice/AdGuardHome.service
           └─1280 /opt/AdGuardHome/AdGuardHome -s run

Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Starting the DNS proxy server
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Ratelimit is enabled and set to 20 rps
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] The server is configured to refuse ANY requests
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] DNS cache is enabled
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Creating the UDP server socket
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Listening to udp://[::]:53
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Creating the TCP server socket
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Listening to tcp://[::]:53
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Entering the UDP listener loop on [::]:53
Nov 22 10:43:03 adguard AdGuardHome[1280]: 2019/11/22 10:43:03 [info] Entering the tcp listener loop on [::]:53

Additional Information

$ cat /etc/systemd/system/AdGuardHome.service

[Unit]
Description=AdGuard Home: Network-level blocker
ConditionFileIsExecutable=/opt/AdGuardHome/AdGuardHome
After=syslog.target network-online.target

[Service]
User=aghome
Group=aghome
StartLimitInterval=5
StartLimitBurst=10
ExecStart=/opt/AdGuardHome/AdGuardHome "-s" "run"

WorkingDirectory=/opt/AdGuardHome

Restart=always
RestartSec=10
EnvironmentFile=-/etc/sysconfig/AdGuardHome

[Install]
WantedBy=multi-user.target
@ameshkov ameshkov added this to the v0.102 milestone Nov 25, 2019
@ameshkov
Copy link
Member

Ideally, AGH would handle setting of the CAP_NET_BIND_SERVICE capability. Baring that, AGH should display a warning message before upgrading via Dashboard.

I am not sure if we can handle this without root.

As an alternative solution, we can simply disable automatic updates, replace "Update now" with "Learn how to update" button and lead users to the update manual.

@kinggrowler
Copy link
Author

kinggrowler commented Nov 25, 2019

I have a workaround for this simply by changing slightly the systemd service file for AdGuardHome.service.

Adding the line:

ExecStartPre=+/sbin/setcap CAP_NET_BIND_SERVICE=+eip /opt/AdGuardHome/AdGuardHome

Will run the setcap command each time the service is bounced, and run only that command as root user. I have tested this a number of times upgrading from 0.99.2 to 0.99.3 and it works without issue. The downside is we run this command each time the service is restarted, but it certainly doesn't cause issue to do so (as far as I know).

See this note about systemd.service "Special executable prefixes:

Prefix Effect
"+" If the executable path is prefixed with "+" then the process is executed with full privileges. In this mode privilege restrictions configured with User=, Group=, CapabilityBoundingSet= or the various file system namespacing options (such as PrivateDevices=, PrivateTmp=) are not applied to the invoked command line (but still affect any other ExecStart=, ExecStop=, … lines).

( The "+" modifier was introduced in version 231.)

Attached is my complete systemd service file, which differs from the one in my orignal comment by only this line:

[Unit]
Description=AdGuard Home: Network-level blocker
ConditionFileIsExecutable=/opt/AdGuardHome/AdGuardHome
After=syslog.target network-online.target

[Service]
User=aghome
Group=aghome
StartLimitInterval=5
StartLimitBurst=10
ExecStartPre=+/sbin/setcap CAP_NET_BIND_SERVICE=+eip /opt/AdGuardHome/AdGuardHome
ExecStart=/opt/AdGuardHome/AdGuardHome "-s" "run"

WorkingDirectory=/opt/AdGuardHome

Restart=always
RestartSec=10
EnvironmentFile=-/etc/sysconfig/AdGuardHome

[Install]
WantedBy=multi-user.target

@ameshkov
Copy link
Member

Huh, interesting!

So it seems it would be enough to change the documentation on how to run AdGuard Home without root.

@kinggrowler
Copy link
Author

Huh, interesting!

So it seems it would be enough to change the documentation on how to run AdGuard Home without root.

My workaround definitely works but I'm not an expert with systemd so I don't know if there is a better way. It certainly seems like a nice solution since there is no change to the AGH binary and only adding one line to the service file, and only for those users that wish to run as non-root user.

@OMICRON3069
Copy link

OMICRON3069 commented Dec 25, 2019

Maybe you can add this two line to your systemd services file

CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

so it will be like

[Service]
Type=simple
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
PermissionsStartOnly=true

ExecStart=/usr/local/bin/AdGuardHome -w /opt/dns
WorkingDirectory=/opt/dns

Restart=on-failure
RestartSec=3

User=omicron3069

KillMode=process
ProtectSystem=full
ProtectHome=read-only
PrivateTmp=true
PrivateDevices=true
NoNewPrivileges=true

@kinggrowler
Copy link
Author

@OMICRON3069 , can you explain a bit more about what these two lines do?

@OMICRON3069
Copy link

No problem, and you can read systemd manual in detail at https://www.freedesktop.org/software/systemd/man/systemd.exec.html

CapabilityBoundingSet=

Controls which capabilities to include in the capability bounding set for the executed process. See capabilities(7) for details. Takes a whitespace-separated list of capability names, e.g. CAP_SYS_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_PTRACE. Capabilities listed will be included in the bounding set, all others are removed. If the list of capabilities is prefixed with "~", all but the listed capabilities will be included, the effect of the assignment inverted. Note that this option also affects the respective capabilities in the effective, permitted and inheritable capability sets. If this option is not used, the capability bounding set is not modified on process execution, hence no limits on the capabilities of the process are enforced. This option may appear more than once, in which case the bounding sets are merged by OR, or by AND if the lines are prefixed with "~" (see below). If the empty string is assigned to this option, the bounding set is reset to the empty capability set, and all prior settings have no effect. If set to "~" (without any further argument), the bounding set is reset to the full set of available capabilities, also undoing any previous settings. This does not affect commands prefixed with "+".

AmbientCapabilities=

Controls which capabilities to include in the ambient capability set for the executed process. Takes a whitespace-separated list of capability names, e.g. CAP_SYS_ADMIN, CAP_DAC_OVERRIDE, CAP_SYS_PTRACE. This option may appear more than once in which case the ambient capability sets are merged (see the above examples in CapabilityBoundingSet=). If the list of capabilities is prefixed with "~", all but the listed capabilities will be included, the effect of the assignment inverted. If the empty string is assigned to this option, the ambient capability set is reset to the empty capability set, and all prior settings have no effect. If set to "~" (without any further argument), the ambient capability set is reset to the full set of available capabilities, also undoing any previous settings. Note that adding capabilities to ambient capability set adds them to the process's inherited capability set.

Ambient capability sets are useful if you want to execute a process as a non-privileged user but still want to give it some capabilities. Note that in this case option keep-caps is automatically added to SecureBits= to retain the capabilities over the user change. AmbientCapabilities= does not affect commands prefixed with "+".

Also, you can get those answers by searching systemd allow bind port with google. I think google should always be the first to ask about those technical problems.

@kinggrowler
Copy link
Author

Very interesting, thanks! What was your experience doing an upgrade?

I think google should always be the first to ask about those technical problems.

I don't disagree but found in my IT career, when creating or updating a ticket, it was a powerful choice to include all information in the ticket so as to prevent people needing to click elsewhere for answers. It's one thing to say "do this" but it's more helpful to include why.

But I'm retired now and no longer do IT work so I don't know what the hot trends are regarding this. Cheers!

@OMICRON3069
Copy link

Things are little different in my situation. I compile those code (not just only AGH) on my local machine, and sync those binaries to my server through syncthing.

Indeed, it makes sense to include additional information to prevent people for further searching, maybe I will try it in the future!

@simonbcn
Copy link

Any of those solutions doesn't work in a new install. AdGuardHome always wants to run as root the first time.

@ameshkov
Copy link
Member

@simonbcn the thing is that AdGuard Home detects the first installation by checking for AdGuardHome.yaml presence. If you create this file by yourself before running AGH, it won't check for root access.

@simonbcn
Copy link

@ameshkov I've tried to create a empty file with that name but it doesn't work:

...
ene 21 15:26:08 juan-pc audit[1]: SERVICE_START pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=adguardhome comm="systemd" exe="/usr/lib/systemd>
ene 21 15:26:08 juan-pc kernel: audit: type=1130 audit(1579616768.611:1971): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=adguardhome comm="s>
ene 21 15:26:08 juan-pc AdGuardHome[203323]: 2020/01/21 15:26:08 [info] Service control action: run
ene 21 15:26:08 juan-pc AdGuardHome[203323]: 2020/01/21 15:26:08 [info] AdGuard Home, version v0.100.9, channel release
ene 21 15:26:08 juan-pc AdGuardHome[203323]: 2020/01/21 15:26:08 [info] AdGuard Home is running as a service
ene 21 15:26:08 juan-pc AdGuardHome[203323]: 2020/01/21 15:26:08 [info] home.upgradeSchema0to1(): called
ene 21 15:26:08 juan-pc AdGuardHome[203323]: 2020/01/21 15:26:08 [info] home.upgradeSchema1to2(): called
ene 21 15:26:08 juan-pc AdGuardHome[203323]: 2020/01/21 15:26:08 [info] home.upgradeSchema2to3(): called
ene 21 15:26:08 juan-pc AdGuardHome[203323]: 2020/01/21 15:26:08 [fatal] DNS configuration is not a map
ene 21 15:26:08 juan-pc systemd[1]: adguardhome.service: Main process exited, code=exited, status=1/FAILURE
-- Subject: Unit process exited
-- Defined-By: systemd
-- Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- An ExecStart= process belonging to unit adguardhome.service has exited.
-- 
-- The process' exit code is 'exited' and its exit status is 1.
ene 21 15:26:08 juan-pc systemd[1]: adguardhome.service: Failed with result 'exit-code'.
-- Subject: Unit failed
-- Defined-By: systemd
-- Support: https://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- The unit adguardhome.service has entered the 'failed' state with result 'exit-code'.
ene 21 15:26:08 juan-pc audit[1]: SERVICE_STOP pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=adguardhome comm="systemd" exe="/usr/lib/systemd/>
...

@szolin
Copy link
Contributor

szolin commented Jan 21, 2020

@simonbcn You should exec AGH under root for the first time. After you finish the installation wizard you'll have a yaml config file. Now you can use this file even on a new PC - root user isn't required anymore.

@simonbcn
Copy link

@szolin but the installation wizard asks for user/password in "Authentication" and this is should be configured for the final user.
I think the start of Adguardhome is wrong because it doesn't check if it really has write access to save that configuration file before it wants to run as root always.

@szolin
Copy link
Contributor

szolin commented Jan 21, 2020

has write access to save that configuration file

We don't do that indeed, but we don't have to. What's the use-case here?

this is should be configured for the final user

The user is AGH administrator - we currently assume that he has root access.

In other words, your use-case is not common. And allowing to start under a normal user will lead to problems for the most users during installation wizard steps - when they choose 53 and 80 ports. Can you maybe propose a good solution to this whole behaviour?

@simonbcn
Copy link

Why AGH needs root rights?

@szolin
Copy link
Contributor

szolin commented Jan 21, 2020

Why AGH needs root rights?

And I've just explained that:

And allowing to start under a normal user will lead to problems for the most users during installation wizard steps - when they choose 53 and 80 ports

@simonbcn
Copy link

So the problem is not that the configuration file does not exist, it is that it needs sudo rights to access those ports. Therefore your proposed solution (to provide a default AGH. config file) would not work.

@szolin
Copy link
Contributor

szolin commented Jan 21, 2020

Why not? It will if you set different port numbers there. You won't be able to listen on ports <1024 without special system configuration, as explained by the thread starter.

@simonbcn
Copy link

That is the problem, this application assumes many things and that is why it calls for sudo rights but it should not be like that. At least, not if we want this program to be portable to any distro linux safely.
setcap CAP_NET_BIND_SERVICE is the solution if I don't want run this program as root. But AGH doesn't test anything before ask for sudo rights.
Perhaps it should do a test after the user configures the URLs and ports and if a problem occurs indicate to the user the need to run as root.
On the other hand, wouldn't it require root rights whenever the program is restarted to open the <1024 ports?

@ameshkov
Copy link
Member

That is the problem, this application assumes many things and that is why it calls for sudo rights but it should not be like that. At least, not if we want this program to be portable to any distro linux safely.

That's why there is #723.

setcap CAP_NET_BIND_SERVICE is the solution if I don't want run this program as root. But AGH doesn't test anything before ask for sudo rights.

@simonbcn besides using ports 53 and 80, AGH also offers to set static IP and that also requires root access.

If we want to make AGH run without root, we'll have to move the initial setup logic to the package, i.e. come up with a bash script that will ask the very same questions (prompt for ports, admin user/password, offer to set static IP), then build the AdGuardHome.yaml, run setcap CAP_NET_BIND_SERVICE, and finally, run AGH itself.

I think that building AdGuardHome.yaml is the most problematic part of this. We could add a command-line option that will do this, something like this:
./AdGuardHome --init --web-port=80 --dns-port=53 --username=admin --password=admin

What do you think?

@simonbcn
Copy link

The problem is not just the configuration file, it is all the files and folders that the initialization process creates with the root user:

...
ene 22 10:39:23 juan-pc AdGuardHome[31340]: 2020/01/22 10:39:23 [info] Service control action: run
ene 22 10:39:23 juan-pc AdGuardHome[31340]: 2020/01/22 10:39:23 [info] AdGuard Home, version v0.100.9, channel release
ene 22 10:39:23 juan-pc AdGuardHome[31340]: 2020/01/22 10:39:23 [info] AdGuard Home is running as a service
ene 22 10:39:23 juan-pc AdGuardHome[31340]: 2020/01/22 10:39:23 [error] Stats: open DB: /var/lib/adguardhome/data/stats.db: open /var/lib/adguardhome>
ene 22 10:39:23 juan-pc AdGuardHome[31340]: 2020/01/22 10:39:23 [fatal] Couldn't initialize statistics module
ene 22 10:39:23 juan-pc systemd[1]: adguardhome.service: Main process exited, code=exited, status=1/FAILURE
...
...
$ tree -pu /var/lib/adguardhome
/var/lib/adguardhome
├── [-rwxr-xr-x adguardhome]  AdGuardHome
├── [-rw-r--r-- adguardhome]  AdGuardHome.yaml
└── [drwxr-xr-x root    ]  data
    ├── [drwxr-xr-x root    ]  filters
    │   └── [-rw-r--r-- root    ]  1.txt
    ├── [-rw-r--r-- root    ]  sessions.db
    └── [-rw-r--r-- root    ]  stats.db
...

@ameshkov
Copy link
Member

The problem is not just the configuration file, it is all the files and folders that the initialization process creates with the root user

The point is that you should not run AGH with root access at all.

You should instead place the AdGuardHome.yaml file near the binary and run AGH under the user you're intending to use after that.

@simonbcn
Copy link

The problem is in the application as it is designed now. I have to run it for the first time as root but that implies that all files/folders created at that time belong to the root user.
I cannot create a generic configuration file for any user who installs this application. That has to be configured by everyone when they first run it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants