diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml
index 556d2535f8..f8185f71ca 100644
--- a/man/varlinkctl.xml
+++ b/man/varlinkctl.xml
@@ -354,6 +354,7 @@
+
diff --git a/shell-completion/bash/varlinkctl b/shell-completion/bash/varlinkctl
index 7d2c850a22..f7d880b464 100644
--- a/shell-completion/bash/varlinkctl
+++ b/shell-completion/bash/varlinkctl
@@ -42,7 +42,7 @@ _varlinkctl() {
local i n verb comps
local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]}
local -A OPTS=(
- [STANDALONE]='-h --help --version --no-pager -q --quiet
+ [STANDALONE]='-h --help --version --no-pager -q --quiet --no-ask-password
--oneway --collect --more --exec -j -E'
[ARG]='--graceful --timeout --push-fd --json'
)
diff --git a/src/shared/varlink-io.systemd.sysext.c b/src/shared/varlink-io.systemd.sysext.c
index 90eb8177d1..13d39a89ff 100644
--- a/src/shared/varlink-io.systemd.sysext.c
+++ b/src/shared/varlink-io.systemd.sysext.c
@@ -1,5 +1,6 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#include "bus-polkit.h"
#include "varlink-io.systemd.sysext.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
@@ -19,24 +20,28 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_INPUT(force, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
- SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
+ SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ VARLINK_DEFINE_POLKIT_INPUT);
static SD_VARLINK_DEFINE_METHOD(
Unmerge,
SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE),
- SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
+ SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ VARLINK_DEFINE_POLKIT_INPUT);
static SD_VARLINK_DEFINE_METHOD(
Refresh,
SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_INPUT(force, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_INPUT(noReload, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
- SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE));
+ SD_VARLINK_DEFINE_INPUT(noexec, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
+ VARLINK_DEFINE_POLKIT_INPUT);
static SD_VARLINK_DEFINE_METHOD_FULL(
List,
SD_VARLINK_REQUIRES_MORE,
SD_VARLINK_DEFINE_INPUT_BY_TYPE(class, ImageClass, SD_VARLINK_NULLABLE),
+ VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Class, ImageClass, 0),
SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(Type, ImageType, 0),
SD_VARLINK_DEFINE_OUTPUT(Name, SD_VARLINK_STRING, 0),
diff --git a/src/sysext/io.systemd.sysext.policy b/src/sysext/io.systemd.sysext.policy
new file mode 100644
index 0000000000..317b9d4378
--- /dev/null
+++ b/src/sysext/io.systemd.sysext.policy
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+ The systemd Project
+ https://systemd.io
+
+
+ Allow managing (merging, unmerging, ...) of system extension images.
+ Authentication is required for an application to manage a system extension image.
+
+ auth_admin
+ auth_admin
+ auth_admin_keep
+
+
+
+
+ Allow reading (listing, ...) of system extension images.
+ Authentication is required for an application to list system extension images.
+
+ yes
+ yes
+ yes
+
+
+
+
+ Allow managing (merging, unmerging, ...) of configuration extension images.
+ Authentication is required for an application to manage a configuration extension image.
+
+ auth_admin
+ auth_admin
+ auth_admin_keep
+
+
+
+
+ Allow reading (listing, ...) of configuration extension images.
+ Authentication is required for an application to list configuration extension images.
+
+ yes
+ yes
+ yes
+
+
+
diff --git a/src/sysext/meson.build b/src/sysext/meson.build
index daaabec777..75d06163c0 100644
--- a/src/sysext/meson.build
+++ b/src/sysext/meson.build
@@ -18,4 +18,7 @@ if conf.get('ENABLE_SYSEXT') == 1
install_symlink('systemd-confext',
pointing_to : 'systemd-sysext',
install_dir : bindir)
+
+ install_data('io.systemd.sysext.policy',
+ install_dir : polkitpolicydir)
endif
diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c
index 2f6bee58c4..7b60dd346b 100644
--- a/src/sysext/sysext.c
+++ b/src/sysext/sysext.c
@@ -14,6 +14,7 @@
#include "blkid-util.h"
#include "blockdev-util.h"
#include "build.h"
+#include "bus-polkit.h"
#include "bus-unit-util.h"
#include "bus-util.h"
#include "capability-util.h"
@@ -117,6 +118,8 @@ static const struct {
const char *full_identifier;
const char *short_identifier;
const char *short_identifier_plural;
+ const char *polkit_rw_action_id;
+ const char *polkit_ro_action_id;
const char *blurb;
const char *dot_directory_name;
const char *directory_name;
@@ -132,6 +135,8 @@ static const struct {
.full_identifier = "systemd-sysext",
.short_identifier = "sysext",
.short_identifier_plural = "extensions",
+ .polkit_rw_action_id = "io.systemd.sysext.manage",
+ .polkit_ro_action_id = "io.systemd.sysext.read",
.blurb = "Merge system extension images into /usr/ and /opt/.",
.dot_directory_name = ".systemd-sysext",
.level_env = "SYSEXT_LEVEL",
@@ -146,6 +151,8 @@ static const struct {
.full_identifier = "systemd-confext",
.short_identifier = "confext",
.short_identifier_plural = "confexts",
+ .polkit_rw_action_id = "io.systemd.confext.manage",
+ .polkit_ro_action_id = "io.systemd.confext.read",
.blurb = "Merge configuration extension images into /etc/.",
.dot_directory_name = ".systemd-confext",
.level_env = "CONFEXT_LEVEL",
@@ -623,13 +630,16 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v
static const sd_json_dispatch_field dispatch_table[] = {
{ "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodUnmergeParameters, class), 0 },
{ "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(MethodUnmergeParameters, no_reload), 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
MethodUnmergeParameters p = {
.no_reload = -1,
};
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **hierarchies = NULL;
ImageClass image_class = arg_image_class;
+ bool no_reload;
int r;
assert(link);
@@ -638,13 +648,24 @@ static int vl_method_unmerge(sd_varlink *link, sd_json_variant *parameters, sd_v
if (r != 0)
return r;
+ no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload;
+
r = parse_image_class_parameter(link, p.class, &image_class, &hierarchies);
if (r < 0)
return r;
- r = unmerge(image_class,
- hierarchies ?: arg_hierarchies,
- p.no_reload >= 0 ? p.no_reload : arg_no_reload);
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ image_class_info[image_class].polkit_rw_action_id,
+ (const char**) STRV_MAKE(
+ "verb", "unmerge",
+ "noReload", one_zero(no_reload)),
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
+ r = unmerge(image_class, hierarchies ?: arg_hierarchies, no_reload);
if (r < 0)
return r;
@@ -2261,6 +2282,7 @@ static int parse_merge_parameters(sd_varlink *link, sd_json_variant *parameters,
{ "force", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodMergeParameters, force), 0 },
{ "noReload", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodMergeParameters, no_reload), 0 },
{ "noexec", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodMergeParameters, noexec), 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
@@ -2272,6 +2294,7 @@ static int parse_merge_parameters(sd_varlink *link, sd_json_variant *parameters,
}
static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
_cleanup_hashmap_free_ Hashmap *images = NULL;
MethodMergeParameters p = {
.force = -1,
@@ -2280,7 +2303,8 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var
};
_cleanup_strv_free_ char **hierarchies = NULL;
ImageClass image_class = arg_image_class;
- int r;
+ bool force, no_reload;
+ int r, noexec;
assert(link);
@@ -2292,6 +2316,23 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var
if (r < 0)
return r;
+ force = p.force >= 0 ? p.force : arg_force;
+ no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload;
+ noexec = p.noexec >= 0 ? p.noexec : arg_noexec;
+
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ image_class_info[image_class].polkit_rw_action_id,
+ (const char**) STRV_MAKE(
+ "verb", "merge",
+ "force", one_zero(force),
+ "noReload", one_zero(no_reload),
+ "noexec", one_zero(noexec > 0)),
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
r = image_discover_and_read_metadata(image_class, &images);
if (r < 0)
return r;
@@ -2306,12 +2347,7 @@ static int vl_method_merge(sd_varlink *link, sd_json_variant *parameters, sd_var
if (r > 0)
return sd_varlink_errorbo(link, "io.systemd.sysext.AlreadyMerged", SD_JSON_BUILD_PAIR_STRING("hierarchy", which));
- r = merge(image_class,
- hierarchies ?: arg_hierarchies,
- p.force >= 0 ? p.force : arg_force,
- p.no_reload >= 0 ? p.no_reload : arg_no_reload,
- p.noexec >= 0 ? p.noexec : arg_noexec,
- images);
+ r = merge(image_class, hierarchies ?: arg_hierarchies, force, no_reload, noexec, images);
if (r < 0)
return r;
@@ -2381,9 +2417,11 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v
.no_reload = -1,
.noexec = -1,
};
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **hierarchies = NULL;
ImageClass image_class = arg_image_class;
- int r;
+ bool force, no_reload;
+ int r, noexec;
assert(link);
@@ -2395,11 +2433,24 @@ static int vl_method_refresh(sd_varlink *link, sd_json_variant *parameters, sd_v
if (r < 0)
return r;
- r = refresh(image_class,
- hierarchies ?: arg_hierarchies,
- p.force >= 0 ? p.force : arg_force,
- p.no_reload >= 0 ? p.no_reload : arg_no_reload,
- p.noexec >= 0 ? p.noexec : arg_noexec);
+ force = p.force >= 0 ? p.force : arg_force;
+ no_reload = p.no_reload >= 0 ? p.no_reload : arg_no_reload;
+ noexec = p.noexec >= 0 ? p.noexec : arg_noexec;
+
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ image_class_info[image_class].polkit_rw_action_id,
+ (const char**) STRV_MAKE(
+ "verb", "refresh",
+ "force", one_zero(force),
+ "noReload", one_zero(no_reload),
+ "noexec", one_zero(noexec > 0)),
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
+ r = refresh(image_class, hierarchies ?: arg_hierarchies, force, no_reload, noexec);
if (r < 0)
return r;
@@ -2445,9 +2496,11 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl
static const sd_json_dispatch_field dispatch_table[] = {
{ "class", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
+ VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ Hashmap **polkit_registry = ASSERT_PTR(userdata);
int r;
assert(link);
@@ -2462,6 +2515,15 @@ static int vl_method_list(sd_varlink *link, sd_json_variant *parameters, sd_varl
if (r < 0)
return r;
+ r = varlink_verify_polkit_async(
+ link,
+ /* bus= */ NULL,
+ image_class_info[image_class].polkit_ro_action_id,
+ (const char**) STRV_MAKE("verb", "list"),
+ polkit_registry);
+ if (r <= 0)
+ return r;
+
_cleanup_hashmap_free_ Hashmap *images = NULL;
r = image_discover(RUNTIME_SCOPE_SYSTEM, image_class, arg_root, &images);
if (r < 0)
@@ -2727,10 +2789,14 @@ static int run(int argc, char *argv[]) {
if (arg_varlink) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
+ _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
/* Invocation as Varlink service */
- r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_ROOT_ONLY, NULL);
+ r = varlink_server_new(
+ &varlink_server,
+ SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA,
+ &polkit_registry);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c
index 865235bb30..1c28005647 100644
--- a/src/varlinkctl/varlinkctl.c
+++ b/src/varlinkctl/varlinkctl.c
@@ -9,6 +9,7 @@
#include "sd-varlink.h"
#include "build.h"
+#include "bus-util.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
@@ -21,6 +22,7 @@
#include "parse-argument.h"
#include "parse-util.h"
#include "pidfd-util.h"
+#include "polkit-agent.h"
#include "pretty-print.h"
#include "process-util.h"
#include "string-util.h"
@@ -46,6 +48,7 @@ static char **arg_graceful = NULL;
static usec_t arg_timeout = 0;
static bool arg_exec = false;
static PushFds arg_push_fds = {};
+static bool arg_ask_password = true;
static void push_fds_done(PushFds *p) {
assert(p);
@@ -87,6 +90,7 @@ static int help(void) {
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Show package version\n"
+ " --no-ask-password Do not prompt for password\n"
" --no-pager Do not pipe output into a pager\n"
" --more Request multiple responses\n"
" --collect Collect multiple responses in a JSON array\n"
@@ -126,21 +130,23 @@ static int parse_argv(int argc, char *argv[]) {
ARG_TIMEOUT,
ARG_EXEC,
ARG_PUSH_FD,
+ ARG_NO_ASK_PASSWORD,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "more", no_argument, NULL, ARG_MORE },
- { "oneway", no_argument, NULL, ARG_ONEWAY },
- { "json", required_argument, NULL, ARG_JSON },
- { "collect", no_argument, NULL, ARG_COLLECT },
- { "quiet", no_argument, NULL, 'q' },
- { "graceful", required_argument, NULL, ARG_GRACEFUL },
- { "timeout", required_argument, NULL, ARG_TIMEOUT },
- { "exec", no_argument, NULL, ARG_EXEC },
- { "push-fd", required_argument, NULL, ARG_PUSH_FD },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "more", no_argument, NULL, ARG_MORE },
+ { "oneway", no_argument, NULL, ARG_ONEWAY },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "collect", no_argument, NULL, ARG_COLLECT },
+ { "quiet", no_argument, NULL, 'q' },
+ { "graceful", required_argument, NULL, ARG_GRACEFUL },
+ { "timeout", required_argument, NULL, ARG_TIMEOUT },
+ { "exec", no_argument, NULL, ARG_EXEC },
+ { "push-fd", required_argument, NULL, ARG_PUSH_FD },
+ { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{},
};
@@ -253,6 +259,10 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
+ case ARG_NO_ASK_PASSWORD:
+ arg_ask_password = false;
+ break;
+
case '?':
return -EINVAL;
@@ -619,6 +629,8 @@ static int verb_call(int argc, char *argv[], void *userdata) {
if (arg_exec && (arg_collect || (arg_method_flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE))) != 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--exec and --collect/--more/--oneway may not be combined.");
+ (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password);
+
url = argv[1];
method = argv[2];
parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh
index 5dd26281ab..47ea82c3b7 100755
--- a/test/units/TEST-50-DISSECT.dissect.sh
+++ b/test/units/TEST-50-DISSECT.dissect.sh
@@ -811,6 +811,33 @@ grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file
varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Unmerge '{}'
(! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file )
+# And again, but unprivileged, if we have pidfds and polkit support
+# Also check that the required policy is installed, as packages might be out of date with the test
+if systemd-analyze compare-versions "$(uname -r)" ge 6.5 && \
+ systemd-analyze compare-versions "$(pkcheck --version | awk '{print $3}')" ge 124 && \
+ test -f /usr/share/polkit-1/actions/io.systemd.sysext.policy; then
+ mkdir -p /etc/polkit-1/rules.d
+ cat >/etc/polkit-1/rules.d/sysext-unpriv.rules <<'EOF'
+polkit.addRule(function(action, subject) {
+ if (action.id == "io.systemd.sysext.manage" &&
+ subject.user == "testuser") {
+ return polkit.Result.YES;
+ }
+});
+EOF
+ systemctl try-reload-or-restart polkit.service
+ run0 -u testuser varlinkctl call --more /run/systemd/io.systemd.sysext io.systemd.sysext.List '{}'
+ (! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file )
+ run0 -u testuser varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Merge '{"allowInteractiveAuthentication": true}'
+ grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file
+ run0 -u testuser varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Refresh '{"allowInteractiveAuthentication": true}'
+ grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file
+ run0 -u testuser varlinkctl call /run/systemd/io.systemd.sysext io.systemd.sysext.Unmerge '{"allowInteractiveAuthentication": true}'
+ (! grep -q -F "MARKER=1" /usr/lib/systemd/system/some_file )
+ rm -f /etc/polkit-1/rules.d/sysext-unpriv.rules
+ systemctl try-reload-or-restart polkit.service
+fi
+
# Check that extensions cannot contain os-release
mkdir -p /run/extensions/app-reject/usr/lib/{extension-release.d/,systemd/system}
echo "ID=_any" >/run/extensions/app-reject/usr/lib/extension-release.d/extension-release.app-reject
diff --git a/test/units/TEST-50-DISSECT.sh b/test/units/TEST-50-DISSECT.sh
index b42aa6e7b3..40b5025297 100755
--- a/test/units/TEST-50-DISSECT.sh
+++ b/test/units/TEST-50-DISSECT.sh
@@ -26,6 +26,8 @@ at_exit() {
rm -rf "$IMAGE_DIR"
+ rm -f /etc/polkit-1/rules.d/sysext-unpriv.rules
+
loginctl disable-linger testuser
}
diff --git a/units/systemd-sysext.socket b/units/systemd-sysext.socket
index 78475cf196..97d1695780 100644
--- a/units/systemd-sysext.socket
+++ b/units/systemd-sysext.socket
@@ -18,7 +18,7 @@ ConditionCapability=CAP_SYS_ADMIN
[Socket]
ListenStream=/run/systemd/io.systemd.sysext
FileDescriptorName=varlink
-SocketMode=0600
+SocketMode=0666
Accept=yes
MaxConnectionsPerSource=16