From 754a9908f41595fd184030b5c121d7bae5f89dc4 Mon Sep 17 00:00:00 2001 From: George Sapkin Date: Wed, 26 Mar 2025 02:04:22 +0200 Subject: [PATCH] adguardhome: run as an unprivileged user Run AdGuard Home without superuser privileges, by granting the binary capabilities through ujail. AdGuard Home writes new config files, so it must have r/w access to the directory where these files live. Which means existing configs must be migrated to a new directory, /etc/adguardhome, by default. CAP_NET_BIND_SERVICE and CAP_NET_RAW capabilities are based on the official documentation linked below. Link: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#running-without-superuser-linux-only Signed-off-by: George Sapkin --- net/adguardhome/Makefile | 17 ++-- net/adguardhome/files/adguardhome.config | 15 +++- net/adguardhome/files/adguardhome.defaults | 93 +++++++++++++++++++++ net/adguardhome/files/adguardhome.init | 95 ++++++++++++++++------ net/adguardhome/files/adguardhome.json | 22 +++++ 5 files changed, 209 insertions(+), 33 deletions(-) create mode 100644 net/adguardhome/files/adguardhome.defaults create mode 100644 net/adguardhome/files/adguardhome.json diff --git a/net/adguardhome/Makefile b/net/adguardhome/Makefile index 6ee8313a62..7634cde786 100644 --- a/net/adguardhome/Makefile +++ b/net/adguardhome/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=adguardhome PKG_VERSION:=0.107.64 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/AdGuardHome/tar.gz/v$(PKG_VERSION)? @@ -46,10 +46,11 @@ define Package/adguardhome TITLE:=Network-wide ads and trackers blocking DNS server URL:=https://github.com/AdguardTeam/AdGuardHome DEPENDS:=$(GO_ARCH_DEPENDS) +ca-bundle + USERID:=adguardhome=853:adguardhome=853 endef define Package/adguardhome/conffiles -/etc/adguardhome.yaml +/etc/adguardhome/adguardhome.yaml /etc/config/adguardhome endef @@ -72,14 +73,20 @@ endef define Package/adguardhome/install $(call GoPackage/Package/Install/Bin,$(1)) - $(INSTALL_DIR) $(1)/etc/init.d - $(INSTALL_BIN) ./files/adguardhome.init $(1)/etc/init.d/adguardhome + $(INSTALL_DIR) $(1)/etc/capabilities + $(INSTALL_CONF) ./files/adguardhome.json $(1)/etc/capabilities/adguardhome.json $(INSTALL_DIR) $(1)/etc/config - $(INSTALL_DATA) ./files/adguardhome.config $(1)/etc/config/adguardhome + $(INSTALL_CONF) ./files/adguardhome.config $(1)/etc/config/adguardhome + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/adguardhome.init $(1)/etc/init.d/adguardhome $(INSTALL_DIR) $(1)/etc/sysctl.d $(INSTALL_CONF) ./files/adguardhome.sysctl $(1)/etc/sysctl.d/50-adguardhome.conf + + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_BIN) ./files/adguardhome.defaults $(1)/etc/uci-defaults/adguardhome endef $(eval $(call Download,adguardhome-frontend)) diff --git a/net/adguardhome/files/adguardhome.config b/net/adguardhome/files/adguardhome.config index 7a8a417d04..293ce6fd40 100644 --- a/net/adguardhome/files/adguardhome.config +++ b/net/adguardhome/files/adguardhome.config @@ -1,5 +1,12 @@ -config adguardhome config - option config /etc/adguardhome.yaml +config adguardhome 'config' + # All paths except for PID must be readable by the configured user + option config '/etc/adguardhome/adguardhome.yaml' # Where to store persistent data by AdGuard Home - option workdir /var/lib/adguardhome - option pidfile /run/adguardhome.pid + option workdir '/var/lib/adguardhome' + option pidfile '/run/adguardhome.pid' + option user 'adguardhome' + option group 'adguardhome' + option verbose '0' + # Files and directories that AdGuard Home has read-only access to + # list jail_mount '/etc/ssl/adguardhome.crt' + # list jail_mount '/etc/ssl/adguardhome.key' diff --git a/net/adguardhome/files/adguardhome.defaults b/net/adguardhome/files/adguardhome.defaults new file mode 100644 index 0000000000..92d8895adb --- /dev/null +++ b/net/adguardhome/files/adguardhome.defaults @@ -0,0 +1,93 @@ +#!/bin/sh + +OLD_CONFIG_FILE=$(uci -q get adguardhome.config.config) +OLD_CONFIG_FILE=${OLD_CONFIG_FILE:-/etc/adguardhome.yaml} +NEW_CONFIG_DIR=/etc/adguardhome +NEW_CONFIG_FILE="$NEW_CONFIG_DIR/adguardhome.yaml" + +start_service() { + if ! /etc/init.d/adguardhome running; then + /etc/init.d/adguardhome start + fi +} + +stop_service() { + if /etc/init.d/adguardhome running; then + /etc/init.d/adguardhome stop + fi +} + +if [ -f "$OLD_CONFIG_FILE" ] && [ "$OLD_CONFIG_FILE" != "$NEW_CONFIG_FILE" ]; then + echo "Old AdGuard Home config found in '$OLD_CONFIG_FILE'" + OLD_CONFIG_DIR=$(dirname "$OLD_CONFIG_FILE") + + USER=$(uci -q get adguardhome.config.user) + USER=${USER:-adguardhome} + GROUP=$(uci -q get adguardhome.config.group) + GROUP=${GROUP:-adguardhome} + + echo "Using $USER:$GROUP for file ownership." + + CUR_CONFIG_FILE="$OLD_CONFIG_FILE" + if [ "$OLD_CONFIG_DIR" = "/etc" ]; then + echo "AdGuard Home config must be stored in its own directory. Migrating..." + stop_service + + [ -d "$NEW_CONFIG_DIR" ] || mkdir -m 0700 -p "$NEW_CONFIG_DIR" + mv "$OLD_CONFIG_FILE" "$NEW_CONFIG_FILE" + chown -R "$USER":"$GROUP" "$NEW_CONFIG_DIR" + CUR_CONFIG_FILE="$NEW_CONFIG_FILE" + uci set adguardhome.config.config="$NEW_CONFIG_FILE" + + echo "Config migrated to '$NEW_CONFIG_FILE'" + + elif [ "$OLD_CONFIG_DIR" != "$NEW_CONFIG_DIR" ]; then + echo "AdGuard Home config is stored in a non-default path. " \ + + "Ensure configured service user '$USER' can access it." + fi + + # Use awk to split match on :, remove double quotes and trim leading and + # trailing spaces + cert_path=$(grep certificate_path: "$CUR_CONFIG_FILE" \ + | awk -F':' '{gsub(/"/, "", $2); gsub(/^ +| +$/, "", $2); print $2}') + if [ -n "$cert_path" ]; then + echo "Found custom 'certificate_path' pointing to '$cert_path'." \ + + "Ensure configured service user '$USER' can access it." + + stop_service + + if ! uci -q show adguardhome.config.jail_mount | grep -q "$cert_path"; then + uci add_list adguardhome.config.jail_mount="$cert_path" + fi + fi + + private_key_path=$(grep private_key_path: "$CUR_CONFIG_FILE" \ + | awk -F':' '{gsub(/"/, "", $2); gsub(/^ +| +$/, "", $2); print $2}') + if [ -n "$private_key_path" ]; then + echo "Found custom 'private_key_path' pointing to '$private_key_path'." \ + + "Ensure configured service user '$USER' can access it." + + stop_service + + if ! uci -q show adguardhome.config.jail_mount | grep -q "$private_key_path"; then + uci add_list adguardhome.config.jail_mount="$private_key_path" + fi + fi + + uci commit adguardhome + start_service + +elif [ "$OLD_CONFIG_FILE" != "$NEW_CONFIG_FILE" ]; then + echo "Old AdGuard Home config not found in '$OLD_CONFIG_FILE'" + stop_service + + # Service script will create the new config directory + uci set adguardhome.config.config="$NEW_CONFIG_FILE" + echo "Config path changed to '$NEW_CONFIG_FILE'" + + uci commit adguardhome + start_service + +else + echo "AdGuard Home config is in its default path '$NEW_CONFIG_FILE'. Nothing to do." +fi diff --git a/net/adguardhome/files/adguardhome.init b/net/adguardhome/files/adguardhome.init index 328ce693d0..dd7e96c17e 100644 --- a/net/adguardhome/files/adguardhome.init +++ b/net/adguardhome/files/adguardhome.init @@ -1,4 +1,5 @@ #!/bin/sh /etc/rc.common +# shellcheck disable=SC3043 # ash supports local PROG=/usr/bin/AdGuardHome @@ -10,34 +11,80 @@ START=19 STOP=89 boot() { - adguardhome_boot=1 - start "$@" + ADGUARDHOME_BOOT=1 + start "$@" } start_service() { - if [ -n "$adguardhome_boot" ]; then - # Do not start yet, wait for triggers - return 0 - fi - - config_load adguardhome - config_get CONFIG_FILE config config "/etc/adguardhome.yaml" - config_get PID_FILE config pidfile "/run/adguardhome.pid" - config_get WORK_DIR config workdir "/var/lib/adguardhome" - - [ -d "$WORK_DIR" ] || mkdir -m 0755 -p "$WORK_DIR" - - procd_open_instance - procd_set_param command "$PROG" -c "$CONFIG_FILE" -w "$WORK_DIR" --pidfile "$PID_FILE" --no-check-update - procd_set_param stdout 1 - procd_set_param stderr 1 - procd_close_instance + if [ -n "$ADGUARDHOME_BOOT" ]; then + # Do not start yet, wait for triggers + return 0 + fi + + local config_file + local group + local pid_file + local user + local verbose + local work_dir + + config_load adguardhome + config_get config_file config config "/etc/adguardhome/adguardhome.yaml" + config_get work_dir config workdir "/var/lib/adguardhome" + config_get pid_file config pidfile "/run/adguardhome.pid" + config_get_bool verbose config verbose + + config_get user config user adguardhome + config_get group config group adguardhome + + local config_dir + config_dir=$(dirname "$config_file") + if [ "$config_dir" = '/etc' ]; then + echo "AdGuard Home config must be stored in its own directory, and not in /etc" >&2 + exit 1 + fi + mkdir -m 0700 -p "$config_dir" + chown -R "$user":"$group" "$config_dir" + + mkdir -m 0700 -p "$work_dir" + chown -R "$user":"$group" "$work_dir" + + procd_open_instance + + procd_set_param command "$PROG" + procd_append_param command --config "$config_file" + procd_append_param command --work-dir "$work_dir" + procd_append_param command --logfile syslog + procd_append_param command --no-check-update + [ "$verbose" = 1 ] && procd_append_param command --verbose + + procd_set_param pidfile "$pid_file" + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_set_param user "$user" + procd_set_param group "$group" + procd_set_param capabilities /etc/capabilities/adguardhome.json + procd_set_param no_new_privs 1 + + # log is needed for logging to syslog instead of stdout + # procfs is needed to readlink /proc/self/exe + procd_add_jail adguardhome log procfs + + # config directory must be writable to write new config files + procd_add_jail_mount_rw "$config_dir" + procd_add_jail_mount_rw "$work_dir" + + procd_add_jail_mount /etc/hosts + procd_add_jail_mount /etc/ssl/certs + config_list_foreach config jail_mount procd_add_jail_mount + + procd_close_instance } service_triggers() { - if [ -n "$adguardhome_boot" ]; then - # Wait for interfaces to be up before starting AdGuard Home for real. - # Prevents issues like https://github.com/openwrt/packages/issues/21868. - procd_add_raw_trigger "interface.*.up" 5000 /etc/init.d/adguardhome restart - fi + if [ -n "$ADGUARDHOME_BOOT" ]; then + # Wait for interfaces to be up before starting AdGuard Home for real. + # Prevents issues like https://github.com/openwrt/packages/issues/21868. + procd_add_raw_trigger "interface.*.up" 5000 /etc/init.d/adguardhome restart + fi } diff --git a/net/adguardhome/files/adguardhome.json b/net/adguardhome/files/adguardhome.json new file mode 100644 index 0000000000..3586a4ae9e --- /dev/null +++ b/net/adguardhome/files/adguardhome.json @@ -0,0 +1,22 @@ +{ + "bounding": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "effective": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "ambient": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "permitted": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ], + "inheritable": [ + "CAP_NET_BIND_SERVICE", + "CAP_NET_RAW" + ] +} -- 2.30.2