1
0
mirror of https://github.com/systemd/systemd synced 2025-11-21 09:44:44 +01:00

Compare commits

...

13 Commits

Author SHA1 Message Date
Lennart Poettering
1353564b9d update TODO 2025-10-08 12:40:04 +02:00
Lennart Poettering
9dcd4113d0
userdb: add support for looking up users or groups by uuid. (#37097)
Followon to #37024.

This implements (mostly) what was suggested there, except that only a
single UUID is accepted (modifying things to support multiple is a
relatively straightforward change from here)

I'm not really convinced this is the right approach:

* I can't really think of any cases where you'd need to query by
multiple UUIDs (I guess you might want to lookup multiple users, but in
that case why aren't there "usernames" or "uids" arrays?)
* If I specify username "foo" and UID 1234 and UID 1234 exists and has
username "bar", I get back the error `ConflictingRecordFound`
* If I specify username "foo" and UUID abcdef... and username "foo"
exists but has UUID 123456..., I get back the error
`NonMatchingRecordFound`

This makes the two ID types behave differently. 

Additionally, when querying by `uuid`, the multiplexer will always sends
`more: true`, which is fine but a little unexpected.

I do think unifying things through the `UserDBMatch` struct could make
sense, but in that case I think it would make sense to unify all query
types in that way (username, uid, uuid), identify when the filter is for
a single or multiple records, and centralise determination of conflict
vs non matching record errors.

`userdb_by_name`/`userdb_by_uid` could then become helper functions for
the simple case where no additional filtering is needed.

Thoughts? 

One other thought: Should the multiplexer just pass through all
parameters, even unknown ones, to the backend services? Even if it
doesn't know how to filter by every property, the backends might, and it
would be useful to allow them to optimise things. (I realise the
disadvantage of this, ofc, is loss of error checking)
2025-10-08 12:03:14 +02:00
Lennart Poettering
ed1c2d1541 test-varlink-idl: add all missing IDL fragments
Let's also order the output alphabetically
2025-10-08 11:31:59 +02:00
Lennart Poettering
c6fb32a944
systemd-creds: varlink null key handling (#39239)
This is split out of #38764 but makes a ton of sense on its own.
2025-10-08 10:41:43 +02:00
Mike Yuan
0294c09e5b json-util: use JSON_VARIANT_ARRAY_FOREACH where appropriate 2025-10-08 09:22:22 +02:00
Mike Yuan
6870f78dcd kernel-install: drop unneeded empty_to_root() 2025-10-08 09:22:07 +02:00
Lennart Poettering
73346e19de creds: normalize definition of "tpm2-absent" legacy key type
This is just an alias, say so and clean up the name of the enum to match
the name of the string representation.
2025-10-08 09:18:59 +02:00
Lennart Poettering
e921d6d40f creds: add explicit control on whether to allow null key decryption
The ability to encrypt/authenticate encryption with a null key was
originally just a fallback concept for cases where during early boot we
have no host key, but the local system has no TPM2. Nowadays it is used
for other stuff as well, such as pcrlock data propagation (i.e. data
that needs no protection itself and required to properly to TPM key
derivation).

Let's give better, explicit control over null key usage, i.e. let's make
it a tristate both on the systemd-creds command line and in the Varlink
IPC to control three cases:

- the default that we allow it only if SecureBoot is off
- explicitly allowed
- explicitly refused (this is new)

Ideally systemd-creds --allow-null switch would take a boolean argument
to control this as a tristate. Alas, that would be a compat break, hence
I added --refuse-null instead (which also maps to the low-level flag for
this).

This also normalizes that the null key is always called "null key" in
messages, and not sometimes "empty key" or "fallback key".
2025-10-08 09:18:28 +02:00
Lennart Poettering
76d5d38fcc test: add testcase for withKey varlink option 2025-10-08 09:18:28 +02:00
Lennart Poettering
097374f3b9 creds: allow varlink clients to choose --with-key= like on the command line 2025-10-08 09:18:28 +02:00
Lennart Poettering
a67084a55e creds: define a proper varlink type for the credential scope 2025-10-08 09:10:42 +02:00
Erin Shepherd
466562c69b userdbctl: add --uuid filtering option
This uses the new UUID-based filtering logic inside the userdb library
to return just the requested user/group record
2025-07-21 19:29:53 +00:00
Erin Shepherd
52874bb763 userdb: add support for looking up users or groups by uuid.
This propagates the UUID lookup parameter through the API permitting
lookups to be done by uuid.
2025-07-21 19:29:42 +00:00
17 changed files with 333 additions and 136 deletions

13
TODO
View File

@ -128,6 +128,19 @@ Deprecations and removals:
Features:
* systemd-repart: add --ghost, that creates file systems, updates the kernel's
partition table but does *not* update partition table on disk. This way, we
have disk backed file systems that go effectively disappear on reboot. This
is useful when booting from a "live" usb stick that is writable, as it means
we do not have to place everything in memory. Moreover, we could then migrate
the file systems to disk later (using btrfs device replacement), if needed as
part of an installer logic.
* add a concept of overlay directory stacks to image discovery, i.e. have a dir
with a name suffix of ".ovl" or so that contains DDIs and plain dirs (and
possible .v dirs) that are glued together on use via overlayfs. one special
subdir should be used as writable top layer.
* journald: log pidfid as another field, i.e. _PIDFDID=
* systemd-nspawn: something like --volatile= but that isn't volatile, but

View File

@ -175,6 +175,7 @@ method GetUserRecord(
dispositionMask: ?[]string,
uidMin: ?int,
uidMax: ?int,
uuid: ?string,
service : string
) -> (
record : object,
@ -188,6 +189,7 @@ method GetGroupRecord(
dispositionMask: ?[]string,
gidMin: ?int,
gidMax: ?int,
uuid: ?string,
service : string
) -> (
record : object,
@ -222,7 +224,7 @@ If neither of the two parameters are set the whole user database is enumerated.
In this case the method call needs to be made with `more` set, so that multiple method call replies may be generated as
effect, each carrying one user record.
The `fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax` fields permit
The `fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax` and `uuid` fields permit
*additional* filtering of the returned set of user records. The `fuzzyNames`
parameter shall be one or more strings that shall be searched for in "fuzzy"
way. What specifically this means is left for the backend to decide, but
@ -232,19 +234,20 @@ carry identifying information for the user. The `dispositionMask` field shall
be one of more user record `disposition` strings. If specified only user
records matching one of the specified dispositions should be enumerated. The
`uidMin` and `uidMax` fields specify a minimum and maximum value for the UID of
returned records. Inline searching for `uid` and `userName` support for
returned records. The `uuid` field specifies to search for the user record associated
with the specified UUID. Inline searching for `uid` and `userName` support for
filtering with these four additional parameters is optional, and clients are
expected to be able to do client-side filtering in case the parameters are not
supported by a service. The service should return the usual `InvalidParameter`
error for the relevant parameter if one is passed and it does not support
it. If a request is made specifying `uid` or `userName` and a suitable record
is found, but the specified filter via `fuzzyNames`, `dispositionMask`,
`uidMin`, or `uidMax` does not match, a `NonMatchingRecordFound` error should
`uidMin`, `uidMax` or `uuid` does not match, a `NonMatchingRecordFound` error should
be returned.
Or to say this differently: the *primary search keys* are
`userName`/`groupName` and `uid`/`gid` and the *secondary search filters* are
`fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax`. If no entry matching
`fuzzyNames`, `dispositionMask`, `uidMin`, `uidMax`, `uuid`. If no entry matching
either of the primary search keys are found `NoRecordFound()` is returned. If
one is found that matches one but not the other primary search key
`ConflictingRecordFound()` is returned. If an entry is found that matches the

View File

@ -424,11 +424,19 @@
<varlistentry>
<term><option>--allow-null</option></term>
<listitem><para>Allow decrypting credentials that use an empty key.</para>
<listitem><para>Allow decrypting credentials that use a null key. By default decryption of credentials encrypted/authenticated with a null key is only allowed if UEFI SecureBoot is off.</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--refuse-null</option></term>
<listitem><para>Refuse decrypting credentials that use a null key, regardless of the UEFI SecureBoot state (see above).</para>
<xi:include href="version-info.xml" xpointer="v259"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--quiet</option></term>
<term><option>-q</option></term>

View File

@ -227,6 +227,15 @@
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--uuid=</option></term>
<listitem><para>When used with the <command>user</command> or <command>group</command> command,
filters the output to the record with the specified UUID. If unspecified, no UUID-based filtering is applied.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--boundaries=</option></term>

View File

@ -72,7 +72,7 @@ static bool arg_pretty = false;
static bool arg_quiet = false;
static bool arg_varlink = false;
static uid_t arg_uid = UID_INVALID;
static bool arg_allow_null = false;
static CredentialFlags arg_credential_flags = 0;
static bool arg_ask_password = true;
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
@ -99,7 +99,7 @@ typedef enum CredKeyType {
CRED_KEY_TYPE_HOST_TPM2_PUBLIC,
CRED_KEY_TYPE_TPM2_PUBLIC_HOST,
CRED_KEY_TYPE_NULL,
CRED_KEY_TYPE_ABSENT,
CRED_KEY_TYPE_TPM2_ABSENT,
_CRED_KEY_TYPE_MAX,
_CRED_KEY_TYPE_INVALID = -EINVAL,
} CredKeyType;
@ -115,7 +115,7 @@ static const char* cred_key_type_table[_CRED_KEY_TYPE_MAX] = {
[CRED_KEY_TYPE_HOST_TPM2_PUBLIC] = "host+tpm2-with-public-key",
[CRED_KEY_TYPE_TPM2_PUBLIC_HOST] = "tpm2-with-public-key+host",
[CRED_KEY_TYPE_NULL] = "null",
[CRED_KEY_TYPE_ABSENT] = "tpm2-absent",
[CRED_KEY_TYPE_TPM2_ABSENT] = "tpm2-absent", /* legacy alias */
};
DEFINE_PRIVATE_STRING_TABLE_LOOKUP(cred_key_type, CredKeyType);
@ -131,7 +131,7 @@ static sd_id128_t cred_key_id[_CRED_KEY_TYPE_MAX] = {
[CRED_KEY_TYPE_HOST_TPM2_PUBLIC] = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
[CRED_KEY_TYPE_TPM2_PUBLIC_HOST] = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
[CRED_KEY_TYPE_NULL] = CRED_AES256_GCM_BY_NULL,
[CRED_KEY_TYPE_ABSENT] = CRED_AES256_GCM_BY_NULL,
[CRED_KEY_TYPE_TPM2_ABSENT] = CRED_AES256_GCM_BY_NULL,
};
static int open_credential_directory(
@ -526,7 +526,7 @@ static int verb_cat(int argc, char **argv, void *userdata) {
timestamp,
uid_is_valid(arg_uid) ? arg_uid : getuid(),
&IOVEC_MAKE(data, size),
CREDENTIAL_ANY_SCOPE,
arg_credential_flags | CREDENTIAL_ANY_SCOPE,
&plaintext);
else
r = decrypt_credential_and_warn(
@ -536,7 +536,7 @@ static int verb_cat(int argc, char **argv, void *userdata) {
arg_tpm2_signature,
uid_is_valid(arg_uid) ? arg_uid : getuid(),
&IOVEC_MAKE(data, size),
CREDENTIAL_ANY_SCOPE,
arg_credential_flags | CREDENTIAL_ANY_SCOPE,
&plaintext);
if (r < 0)
return r;
@ -608,7 +608,7 @@ static int verb_encrypt(int argc, char **argv, void *userdata) {
arg_not_after,
arg_uid,
&plaintext,
arg_ask_password ? CREDENTIAL_IPC_ALLOW_INTERACTIVE : 0,
arg_credential_flags,
&output);
} else
r = encrypt_credential_and_warn(
@ -622,7 +622,7 @@ static int verb_encrypt(int argc, char **argv, void *userdata) {
arg_tpm2_public_key_pcr_mask,
arg_uid,
&plaintext,
/* flags= */ 0,
arg_credential_flags,
&output);
if (r < 0)
return r;
@ -713,7 +713,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) {
timestamp,
arg_uid,
&input,
arg_ask_password ? CREDENTIAL_IPC_ALLOW_INTERACTIVE : 0,
arg_credential_flags,
&plaintext);
} else
r = decrypt_credential_and_warn(
@ -723,7 +723,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) {
arg_tpm2_signature,
arg_uid,
&input,
arg_allow_null ? CREDENTIAL_ALLOW_NULL : 0,
arg_credential_flags,
&plaintext);
if (r < 0)
return r;
@ -815,7 +815,8 @@ static int verb_help(int argc, char **argv, void *userdata) {
" Specify signature for public key PCR policy\n"
" --user Select user-scoped credential encryption\n"
" --uid=UID Select user for scoped credentials\n"
" --allow-null Allow decrypting credentials with empty key\n"
" --allow-null Allow decrypting credentials with null key\n"
" --refuse-null Refuse decrypting credentials with null key\n"
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link,
@ -849,6 +850,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_USER,
ARG_UID,
ARG_ALLOW_NULL,
ARG_REFUSE_NULL,
ARG_NO_ASK_PASSWORD,
};
@ -875,6 +877,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "user", no_argument, NULL, ARG_USER },
{ "uid", required_argument, NULL, ARG_UID },
{ "allow-null", no_argument, NULL, ARG_ALLOW_NULL },
{ "refuse-null", no_argument, NULL, ARG_REFUSE_NULL },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{}
};
@ -1068,7 +1071,13 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_ALLOW_NULL:
arg_allow_null = true;
arg_credential_flags &= ~CREDENTIAL_REFUSE_NULL;
arg_credential_flags |= CREDENTIAL_ALLOW_NULL;
break;
case ARG_REFUSE_NULL:
arg_credential_flags |= CREDENTIAL_REFUSE_NULL;
arg_credential_flags &= ~CREDENTIAL_ALLOW_NULL;
break;
case ARG_NO_ASK_PASSWORD:
@ -1087,6 +1096,8 @@ static int parse_argv(int argc, char *argv[]) {
}
}
SET_FLAG(arg_credential_flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE, arg_ask_password);
if (uid_is_valid(arg_uid)) {
/* If a UID is specified, then switch to scoped credentials */
@ -1167,6 +1178,7 @@ typedef struct MethodEncryptParameters {
uint64_t timestamp;
uint64_t not_after;
CredentialScope scope;
sd_id128_t with_key;
uid_t uid;
} MethodEncryptParameters;
@ -1220,6 +1232,71 @@ static int settle_scope(
return 0;
}
static bool normalize_separator(char c) {
if (IN_SET(c, '-', '+', '_'))
return '_';
return c;
}
static bool enum_name_equal(const char *x, const char *y) {
if (x == y)
return true;
if (!x || !y)
return false;
for (;; x++, y++) {
char a = normalize_separator(*x), b = normalize_separator(*y);
if (a != b)
return false;
if (a == 0)
return true;
}
}
static CredKeyType cred_key_type_from_string_harder(const char *s) {
if (!s)
return _CRED_KEY_TYPE_INVALID;
CredKeyType t = cred_key_type_from_string(s);
if (t >= 0)
return t;
/* Varlink doesn't like dashes and plusses in enum names. Try to match when considering them equal to underscores */
for (t = 0; t < _CRED_KEY_TYPE_MAX; t++)
if (enum_name_equal(cred_key_type_table[t], s))
return t;
return _CRED_KEY_TYPE_INVALID;
}
static int dispatch_credential_key_type(
const char *name,
sd_json_variant *variant,
sd_json_dispatch_flags_t flags,
void *userdata) {
sd_id128_t *id = ASSERT_PTR(userdata);
if (sd_json_variant_is_null(variant)) {
*id = SD_ID128_NULL;
return 0;
}
const char *s = sd_json_variant_string(variant);
if (isempty(s)) {
*id = SD_ID128_NULL;
return 0;
}
CredKeyType t = cred_key_type_from_string_harder(s);
if (t < 0)
return json_log(variant, flags, t, "JSON field '%s' is not a valid key type.", strna(name));
*id = cred_key_id[t];
return 0;
}
static int vl_method_encrypt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
@ -1229,6 +1306,7 @@ static int vl_method_encrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
{ "timestamp", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodEncryptParameters, timestamp), 0 },
{ "notAfter", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodEncryptParameters, not_after), 0 },
{ "scope", SD_JSON_VARIANT_STRING, dispatch_credential_scope, offsetof(MethodEncryptParameters, scope), 0 },
{ "withKey", SD_JSON_VARIANT_STRING, dispatch_credential_key_type, offsetof(MethodEncryptParameters, with_key), SD_JSON_NULLABLE },
{ "uid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(MethodEncryptParameters, uid), 0 },
VARLINK_DISPATCH_POLKIT_FIELD,
{}
@ -1289,7 +1367,7 @@ static int vl_method_encrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
}
r = encrypt_credential_and_warn(
p.scope == CREDENTIAL_USER ? _CRED_AUTO_SCOPED : _CRED_AUTO,
sd_id128_is_null(p.with_key) ? (p.scope == CREDENTIAL_USER ? _CRED_AUTO_SCOPED : _CRED_AUTO) : p.with_key,
p.name,
p.timestamp,
p.not_after,
@ -1324,6 +1402,7 @@ typedef struct MethodDecryptParameters {
uint64_t timestamp;
CredentialScope scope;
uid_t uid;
int allow_null;
} MethodDecryptParameters;
static void method_decrypt_parameters_done(MethodDecryptParameters *p) {
@ -1335,11 +1414,12 @@ static void method_decrypt_parameters_done(MethodDecryptParameters *p) {
static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
static const sd_json_dispatch_field dispatch_table[] = {
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
{ "blob", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), SD_JSON_MANDATORY },
{ "timestamp", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
{ "scope", SD_JSON_VARIANT_STRING, dispatch_credential_scope, offsetof(MethodDecryptParameters, scope), 0 },
{ "uid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(MethodDecryptParameters, uid), 0 },
{ "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 },
{ "blob", SD_JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), SD_JSON_MANDATORY },
{ "timestamp", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 },
{ "scope", SD_JSON_VARIANT_STRING, dispatch_credential_scope, offsetof(MethodDecryptParameters, scope), 0 },
{ "uid", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(MethodDecryptParameters, uid), 0 },
{ "allowNull", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodDecryptParameters, allow_null), SD_JSON_NULLABLE },
VARLINK_DISPATCH_POLKIT_FIELD,
{}
};
@ -1347,6 +1427,7 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
.timestamp = UINT64_MAX,
.scope = _CREDENTIAL_SCOPE_INVALID,
.uid = UID_INVALID,
.allow_null = -1,
};
bool timestamp_fresh, any_scope_after_polkit = false;
_cleanup_(iovec_done_erase) struct iovec output = {};
@ -1373,6 +1454,11 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
if (r < 0)
return r;
if (p.allow_null > 0)
cflags |= CREDENTIAL_ALLOW_NULL;
else if (p.allow_null == 0)
cflags |= CREDENTIAL_REFUSE_NULL;
r = sd_varlink_get_peer_uid(link, &peer_uid);
if (r < 0)
return r;

View File

@ -222,9 +222,9 @@ static int context_open_root(Context *c) {
if (r > 0)
return 0;
c->rfd = open(empty_to_root(arg_root), O_CLOEXEC | O_DIRECTORY | O_PATH);
c->rfd = open(arg_root, O_CLOEXEC | O_DIRECTORY | O_PATH);
if (c->rfd < 0)
return log_error_errno(errno, "Failed to open root directory '%s': %m", empty_to_root(arg_root));
return log_error_errno(errno, "Failed to open root directory '%s': %m", arg_root);
return 0;
}

View File

@ -554,8 +554,8 @@ int json_dispatch_devnum(const char *name, sd_json_variant *variant, sd_json_dis
}
int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) {
char ***l = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **n = NULL;
char ***l = userdata;
int r;
if (sd_json_variant_is_null(variant)) {
@ -566,20 +566,19 @@ int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, s
if (!sd_json_variant_is_array(variant))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name));
for (size_t i = 0; i < sd_json_variant_elements(variant); i++) {
sd_json_variant *e;
const char *a;
sd_json_variant *i;
JSON_VARIANT_ARRAY_FOREACH(i, variant) {
const char *e;
e = sd_json_variant_by_index(variant, i);
if (!sd_json_variant_is_string(e))
if (!sd_json_variant_is_string(i))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name));
assert_se(a = sd_json_variant_string(e));
e = ASSERT_PTR(sd_json_variant_string(i));
if (!env_assignment_is_valid(e))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL),
"JSON field '%s' contains invalid environment variable assignment.", strna(name));
if (!env_assignment_is_valid(a))
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of environment variables.", strna(name));
r = strv_env_replace_strdup(&n, a);
r = strv_env_replace_strdup(&n, e);
if (r < 0)
return json_log_oom(variant, flags);
}

View File

@ -812,6 +812,9 @@ int encrypt_credential_and_warn(
assert(iovec_is_valid(input));
assert(ret);
/* Only one of these two flags may be set at the same time */
assert(!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL) || !FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL));
if (!sd_id128_in_set(with_key,
_CRED_AUTO,
_CRED_AUTO_INITRD,
@ -1008,8 +1011,12 @@ int encrypt_credential_and_warn(
} else
id = with_key;
if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided.");
if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL)) {
if (FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL))
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Attempted to encrypt with null key, but this is disallowed.");
if (!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL))
log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided.");
}
/* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */
r = sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md);
@ -1201,12 +1208,15 @@ int decrypt_credential_and_warn(
assert(iovec_is_valid(input));
assert(ret);
/* Only one of these two flags may be set at the same time */
assert(!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL) || !FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL));
/* Relevant error codes:
*
* -EBADMSG Corrupted file
* -EOPNOTSUPP Unsupported file type (could be: requires TPM but we have no TPM)
* -EHOSTDOWN Need PCR signature file, but couldn't find it
* -EHWPOISON Attempt to decode NULL key (and CREDENTIAL_ALLOW_NULL is off), but the system has a TPM and SecureBoot is on
* -EHWPOISON Attempt to unlock with NULL key and either CREDENTIAL_ALLOW_REFUSE is on, or CREDENTIAL_ALLOW_NULL is off, but the system has a TPM and SecureBoot is on
* -EMEDIUMTYPE File has unexpected scope, i.e. user-scoped credential is attempted to be unlocked in system scope, or vice versa
* -EDESTADDRREQ Credential is incorrectly named (i.e. the authenticated name does not match the actual name)
* -ESTALE Credential's validity has passed
@ -1237,24 +1247,30 @@ int decrypt_credential_and_warn(
return log_error_errno(r, "Failed to load PCR signature: %m");
}
if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) {
/* So this is a credential encrypted with a zero length key. We support this to cover for the
* case where neither a host key not a TPM2 are available (specifically: initrd environments
* where the host key is not yet accessible and no TPM2 chip exists at all), to minimize
* different codeflow for TPM2 and non-TPM2 codepaths. Of course, credentials encoded this
* way offer no confidentiality nor authenticity. Because of that it's important we refuse to
* use them on systems that actually *do* have a TPM2 chip if we are in SecureBoot
* mode. Otherwise an attacker could hand us credentials like this and we'd use them thinking
* they are trusted, even though they are not. */
if (with_null) {
if (FLAGS_SET(flags, CREDENTIAL_REFUSE_NULL))
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
"Credential uses null key, but that's not allowed, refusing.");
if (efi_has_tpm2()) {
if (is_efi_secure_boot())
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
"Credential uses fixed key for fallback use when TPM2 is absent — but TPM2 is present, and SecureBoot is enabled, refusing.");
if (!FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) {
/* So this is a credential encrypted with a zero length key. We support this to cover for the
* case where neither a host key not a TPM2 are available (specifically: initrd environments
* where the host key is not yet accessible and no TPM2 chip exists at all), to minimize
* different codeflow for TPM2 and non-TPM2 codepaths. Of course, credentials encoded this
* way offer no confidentiality nor authenticity. Because of that it's important we refuse to
* use them on systems that actually *do* have a TPM2 chip if we are in SecureBoot
* mode. Otherwise an attacker could hand us credentials like this and we'd use them thinking
* they are trusted, even though they are not. */
log_warning("Credential uses fixed key for use when TPM2 is absent, but TPM2 is present! Accepting anyway, since SecureBoot is disabled.");
} else
log_debug("Credential uses fixed key for use when TPM2 is absent, and TPM2 indeed is absent. Accepting.");
if (efi_has_tpm2()) {
if (is_efi_secure_boot())
return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON),
"Credential uses null key intended for fallback use when TPM2 is absent — but TPM2 is present, and SecureBoot is enabled, refusing.");
log_warning("Credential uses null key intended for use when TPM2 is absent, but TPM2 is present! Accepting anyway, since SecureBoot is disabled.");
} else
log_debug("Credential uses null key intended for use when TPM2 is absent, and TPM2 indeed is absent. Accepting.");
}
}
if (with_scope) {
@ -1590,6 +1606,7 @@ int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after,
SD_JSON_BUILD_PAIR_CONDITION(not_after != USEC_INFINITY, "notAfter", SD_JSON_BUILD_UNSIGNED(not_after)),
SD_JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", SD_JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")),
SD_JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", SD_JSON_BUILD_UNSIGNED(uid)),
SD_JSON_BUILD_PAIR_CONDITION((flags & (CREDENTIAL_ALLOW_NULL|CREDENTIAL_REFUSE_NULL)) != 0, "allowNull", SD_JSON_BUILD_BOOLEAN(flags & CREDENTIAL_ALLOW_NULL)),
SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, CREDENTIAL_IPC_ALLOW_INTERACTIVE)));
if (r < 0)
return log_error_errno(r, "Failed to call Encrypt() varlink call.");

View File

@ -54,11 +54,12 @@ int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret);
int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed);
typedef enum CredentialFlags {
CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */
CREDENTIAL_ANY_SCOPE = 1 << 1, /* allow decryption of both system and user credentials */
CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption with NULL key, even if TPM is around */
CREDENTIAL_REFUSE_NULL = 1 << 1, /* deny decryption with NULL key, even if SecureBoot is off */
CREDENTIAL_ANY_SCOPE = 1 << 2, /* allow decryption of both system and user credentials */
/* Only used by ipc_{encrypt,decrypt}_credential */
CREDENTIAL_IPC_ALLOW_INTERACTIVE = 1 << 2,
CREDENTIAL_IPC_ALLOW_INTERACTIVE = 1 << 3,
} CredentialFlags;
/* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of

View File

@ -2777,7 +2777,8 @@ bool userdb_match_is_set(const UserDBMatch *match) {
return !strv_isempty(match->fuzzy_names) ||
!FLAGS_SET(match->disposition_mask, USER_DISPOSITION_MASK_ALL) ||
match->uid_min > 0 ||
match->uid_max < UID_INVALID-1;
match->uid_max < UID_INVALID-1 ||
!sd_id128_is_null(match->uuid);
}
void userdb_match_done(UserDBMatch *match) {
@ -2836,6 +2837,9 @@ bool user_record_match(UserRecord *u, const UserDBMatch *match) {
if (!BIT_SET(match->disposition_mask, user_record_disposition(u)))
return false;
if (!sd_id128_is_null(match->uuid) && !sd_id128_equal(match->uuid, u->uuid))
return false;
if (!strv_isempty(match->fuzzy_names)) {
/* Note this array of names is sparse, i.e. various entries listed in it will be

View File

@ -513,6 +513,7 @@ typedef struct UserDBMatch {
uid_t uid_max;
gid_t gid_max;
};
sd_id128_t uuid;
} UserDBMatch;
#define USER_DISPOSITION_MASK_ALL ((UINT64_C(1) << _USER_DISPOSITION_MAX) - UINT64_C(1))
@ -522,6 +523,7 @@ typedef struct UserDBMatch {
.disposition_mask = USER_DISPOSITION_MASK_ALL, \
.uid_min = 0, \
.uid_max = UID_INVALID-1, \
.uuid = SD_ID128_NULL, \
}
/* Maybe useful when we want to resolve root and system user/group but want to refuse nobody user/group. */

View File

@ -799,27 +799,29 @@ nomatch:
return 0;
}
static int query_append_disposition_mask(sd_json_variant **query, uint64_t mask) {
static int query_append_common(sd_json_variant **query, const UserDBMatch *match) {
int r;
_cleanup_strv_free_ char **dispositions = NULL;
assert(query);
if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL))
return 0;
uint64_t mask = match->disposition_mask;
if (FLAGS_SET(mask, USER_DISPOSITION_MASK_ALL)) {
for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
if (!BIT_SET(mask, d))
continue;
_cleanup_strv_free_ char **dispositions = NULL;
for (UserDisposition d = 0; d < _USER_DISPOSITION_MAX; d++) {
if (!BITS_SET(mask, d))
continue;
r = strv_extend(&dispositions, user_disposition_to_string(d));
if (r < 0)
return r;
r = strv_extend(&dispositions, user_disposition_to_string(d));
if (r < 0)
return r;
}
}
return sd_json_variant_merge_objectbo(
query,
SD_JSON_BUILD_PAIR_STRV("dispositionMask", dispositions));
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
SD_JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(match->uuid), "uuid", SD_JSON_BUILD_UUID(match->uuid)),
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(dispositions), "dispositionMask", SD_JSON_BUILD_STRV(dispositions)));
}
static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *match) {
@ -832,13 +834,12 @@ static int query_append_uid_match(sd_json_variant **query, const UserDBMatch *ma
r = sd_json_variant_merge_objectbo(
query,
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
SD_JSON_BUILD_PAIR_CONDITION(match->uid_min > 0, "uidMin", SD_JSON_BUILD_UNSIGNED(match->uid_min)),
SD_JSON_BUILD_PAIR_CONDITION(match->uid_max < UID_INVALID-1, "uidMax", SD_JSON_BUILD_UNSIGNED(match->uid_max)));
if (r < 0)
return r;
return query_append_disposition_mask(query, match->disposition_mask);
return query_append_common(query, match);
}
static int userdb_by_name_fallbacks(
@ -1298,13 +1299,13 @@ static int query_append_gid_match(sd_json_variant **query, const UserDBMatch *ma
r = sd_json_variant_merge_objectbo(
query,
SD_JSON_BUILD_PAIR_CONDITION(!strv_isempty(match->fuzzy_names), "fuzzyNames", SD_JSON_BUILD_STRV(match->fuzzy_names)),
SD_JSON_BUILD_PAIR_CONDITION(match->gid_min > 0, "gidMin", SD_JSON_BUILD_UNSIGNED(match->gid_min)),
SD_JSON_BUILD_PAIR_CONDITION(match->gid_max < GID_INVALID-1, "gidMax", SD_JSON_BUILD_UNSIGNED(match->gid_max)));
if (r < 0)
return r;
return query_append_disposition_mask(query, match->disposition_mask);
return query_append_common(query, match);
}
static int groupdb_by_name_fallbacks(

View File

@ -3,6 +3,32 @@
#include "bus-polkit.h"
#include "varlink-io.systemd.Credentials.h"
static SD_VARLINK_DEFINE_ENUM_TYPE(
Scope,
SD_VARLINK_FIELD_COMMENT("Generate a system-bound credential"),
SD_VARLINK_DEFINE_ENUM_VALUE(system),
SD_VARLINK_FIELD_COMMENT("Generate a system and user bound credential"),
SD_VARLINK_DEFINE_ENUM_VALUE(user));
static SD_VARLINK_DEFINE_ENUM_TYPE(
WithKey,
SD_VARLINK_FIELD_COMMENT("Automatically pick the key to bind the credential to"),
SD_VARLINK_DEFINE_ENUM_VALUE(auto),
SD_VARLINK_FIELD_COMMENT("Automatically pick the key to bind the credential to, but ensure it is accessible in the initrd, thus potentially leaving it unencrypted."),
SD_VARLINK_DEFINE_ENUM_VALUE(auto_initrd),
SD_VARLINK_FIELD_COMMENT("Bind to the host key only, i.e. not the TPM"),
SD_VARLINK_DEFINE_ENUM_VALUE(host),
SD_VARLINK_FIELD_COMMENT("Bind to the TPM only, not the host key"),
SD_VARLINK_DEFINE_ENUM_VALUE(tpm2),
SD_VARLINK_FIELD_COMMENT("Bind to the TPM only (using a public key identifying the UKI), not the host key"),
SD_VARLINK_DEFINE_ENUM_VALUE(tpm2_with_public_key),
SD_VARLINK_FIELD_COMMENT("Bind to both the TPM and the host key"),
SD_VARLINK_DEFINE_ENUM_VALUE(host_tpm2),
SD_VARLINK_FIELD_COMMENT("Bind to both the TPM (using a public key identifying the UKI) and the host key"),
SD_VARLINK_DEFINE_ENUM_VALUE(host_tpm2_with_public_key),
SD_VARLINK_FIELD_COMMENT("Do not bind to either host key nor the TPM, thus using null encryption (this provides no authenticity nor confidentiality guarantees)"),
SD_VARLINK_DEFINE_ENUM_VALUE(null));
static SD_VARLINK_DEFINE_METHOD(
Encrypt,
SD_VARLINK_FIELD_COMMENT("The name for the encrypted credential, a string suitable for inclusion in a file name. If not specified no name is encoded in the credential. Typically, if this credential is stored on disk, this is how the file should be called, and permits authentication of the filename."),
@ -16,7 +42,9 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_FIELD_COMMENT("Timestamp when to the credential should be considered invalid. In µs since the UNIX epoch. If not specified, the credential remains valid forever."),
SD_VARLINK_DEFINE_INPUT(notAfter, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The intended scope for the credential. One of 'system' or 'user'. If not specified defaults to 'system', unless an uid is specified (see below), in which case it default to 'user'."),
SD_VARLINK_DEFINE_INPUT(scope, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(scope, Scope, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("Selects the type of key to encrypt this with"),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(withKey, WithKey, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The numeric UNIX UID of the user the credential shall be scoped to. Only relevant if 'user' scope is selected (see above). If not specified and 'user' scope is selected defaults to the UID of the calling user, if that can be determined."),
SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
VARLINK_DEFINE_POLKIT_INPUT,
@ -32,9 +60,11 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_FIELD_COMMENT("The timestamp to use when validating the credential's time validity range. If not specified the current time is used."),
SD_VARLINK_DEFINE_INPUT(timestamp, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("The scope for this credential. If not specified no restrictions on the credential scope are made."),
SD_VARLINK_DEFINE_INPUT(scope, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_INPUT_BY_TYPE(scope, Scope, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("If the 'user' scope is selected, specifies the numeric UNIX UID of the user the credential is associated with. If not specified this is automatically derived from the UID of the calling user, if that can be determined."),
SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("If true allows decryption of credentials encrypted with the null key, if false does not allow it, if unset/null the default depends on whether a TPM device exists and SecureBoot is enabled."),
SD_VARLINK_DEFINE_INPUT(allowNull, SD_VARLINK_BOOL, SD_VARLINK_NULLABLE),
VARLINK_DEFINE_POLKIT_INPUT,
SD_VARLINK_FIELD_COMMENT("The decrypted plaintext data in Base64 encoding."),
SD_VARLINK_DEFINE_OUTPUT(data, SD_VARLINK_STRING, 0));
@ -54,6 +84,10 @@ SD_VARLINK_DEFINE_INTERFACE(
io_systemd_Credentials,
"io.systemd.Credentials",
SD_VARLINK_INTERFACE_COMMENT("APIs for encrypting and decrypting service credentials."),
SD_VARLINK_SYMBOL_COMMENT("The intended scope for the credential."),
&vl_type_Scope,
SD_VARLINK_SYMBOL_COMMENT("Selects the key(s) to encrypt the credentials with."),
&vl_type_WithKey,
SD_VARLINK_SYMBOL_COMMENT("Encrypts some plaintext data, returns an encrypted credential."),
&vl_method_Encrypt,
SD_VARLINK_SYMBOL_COMMENT("Decrypts an encrypted credential, returns plaintext data."),

View File

@ -16,19 +16,21 @@
#include "varlink-io.systemd.BootControl.h"
#include "varlink-io.systemd.Credentials.h"
#include "varlink-io.systemd.FactoryReset.h"
#include "varlink-io.systemd.Hostname.h"
#include "varlink-io.systemd.Import.h"
#include "varlink-io.systemd.Journal.h"
#include "varlink-io.systemd.Login.h"
#include "varlink-io.systemd.Machine.h"
#include "varlink-io.systemd.MachineImage.h"
#include "varlink-io.systemd.Manager.h"
#include "varlink-io.systemd.ManagedOOM.h"
#include "varlink-io.systemd.Manager.h"
#include "varlink-io.systemd.MountFileSystem.h"
#include "varlink-io.systemd.MuteConsole.h"
#include "varlink-io.systemd.NamespaceResource.h"
#include "varlink-io.systemd.Network.h"
#include "varlink-io.systemd.PCRExtend.h"
#include "varlink-io.systemd.PCRLock.h"
#include "varlink-io.systemd.Repart.h"
#include "varlink-io.systemd.Resolve.h"
#include "varlink-io.systemd.Resolve.Monitor.h"
#include "varlink-io.systemd.Udev.h"
@ -163,61 +165,46 @@ static void test_parse_format_one(const sd_varlink_interface *iface) {
}
TEST(parse_format) {
test_parse_format_one(&vl_interface_org_varlink_service);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_UserDatabase);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_MuteConsole);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_NamespaceResource);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Journal);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Resolve);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Resolve_Monitor);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_ManagedOOM);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_MountFileSystem);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Network);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_oom);
print_separator();
test_parse_format_one(&vl_interface_io_systemd);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_PCRExtend);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_PCRLock);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_service);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_sysext);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Credentials);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_BootControl);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Import);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Machine);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_MachineImage);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Manager);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_AskPassword);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Udev);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Login);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_FactoryReset);
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Unit);
print_separator();
test_parse_format_one(&vl_interface_xyz_test);
const sd_varlink_interface* const list[] = {
&vl_interface_io_systemd,
&vl_interface_io_systemd_AskPassword,
&vl_interface_io_systemd_BootControl,
&vl_interface_io_systemd_Credentials,
&vl_interface_io_systemd_FactoryReset,
&vl_interface_io_systemd_Hostname,
&vl_interface_io_systemd_Import,
&vl_interface_io_systemd_Journal,
&vl_interface_io_systemd_Login,
&vl_interface_io_systemd_Machine,
&vl_interface_io_systemd_MachineImage,
&vl_interface_io_systemd_ManagedOOM,
&vl_interface_io_systemd_Manager,
&vl_interface_io_systemd_MountFileSystem,
&vl_interface_io_systemd_MuteConsole,
&vl_interface_io_systemd_NamespaceResource,
&vl_interface_io_systemd_Network,
&vl_interface_io_systemd_PCRExtend,
&vl_interface_io_systemd_PCRLock,
&vl_interface_io_systemd_Repart,
&vl_interface_io_systemd_Resolve,
&vl_interface_io_systemd_Resolve_Monitor,
&vl_interface_io_systemd_Udev,
&vl_interface_io_systemd_Unit,
&vl_interface_io_systemd_UserDatabase,
&vl_interface_io_systemd_oom,
&vl_interface_io_systemd_service,
&vl_interface_io_systemd_sysext,
&vl_interface_org_varlink_service,
&vl_interface_xyz_test,
};
bool sep = false;
FOREACH_ELEMENT(i, list) {
if (sep)
print_separator();
test_parse_format_one(*i);
sep = true;
}
}
TEST(parse) {

View File

@ -57,6 +57,7 @@ static bool arg_chain = false;
static uint64_t arg_disposition_mask = UINT64_MAX;
static uid_t arg_uid_min = 0;
static uid_t arg_uid_max = UID_INVALID-1;
static sd_id128_t arg_uuid = SD_ID128_NULL;
static bool arg_fuzzy = false;
static bool arg_boundaries = true;
static sd_json_variant *arg_from_file = NULL;
@ -411,7 +412,7 @@ static int display_user(int argc, char *argv[], void *userdata) {
int ret = 0, r;
if (arg_output < 0)
arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) || !sd_id128_is_null(arg_uuid) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
if (arg_output == OUTPUT_TABLE) {
table = table_new(" ", "name", "disposition", "uid", "gid", "realname", "home", "shell", "order");
@ -431,6 +432,7 @@ static int display_user(int argc, char *argv[], void *userdata) {
.disposition_mask = arg_disposition_mask,
.uid_min = arg_uid_min,
.uid_max = arg_uid_max,
.uuid = arg_uuid,
};
if (arg_from_file) {
@ -753,7 +755,7 @@ static int display_group(int argc, char *argv[], void *userdata) {
int ret = 0, r;
if (arg_output < 0)
arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
arg_output = arg_from_file || (argc > 1 && !arg_fuzzy) || !sd_id128_is_null(arg_uuid) ? OUTPUT_FRIENDLY : OUTPUT_TABLE;
if (arg_output == OUTPUT_TABLE) {
table = table_new(" ", "name", "disposition", "gid", "description", "order");
@ -772,6 +774,7 @@ static int display_group(int argc, char *argv[], void *userdata) {
.disposition_mask = arg_disposition_mask,
.gid_min = arg_uid_min,
.gid_max = arg_uid_max,
.uuid = arg_uuid,
};
if (arg_from_file) {
@ -1588,6 +1591,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CHAIN,
ARG_UID_MIN,
ARG_UID_MAX,
ARG_UUID,
ARG_DISPOSITION,
ARG_BOUNDARIES,
};
@ -1608,6 +1612,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "chain", no_argument, NULL, ARG_CHAIN },
{ "uid-min", required_argument, NULL, ARG_UID_MIN },
{ "uid-max", required_argument, NULL, ARG_UID_MAX },
{ "uuid", required_argument, NULL, ARG_UUID },
{ "fuzzy", no_argument, NULL, 'z' },
{ "disposition", required_argument, NULL, ARG_DISPOSITION },
{ "boundaries", required_argument, NULL, ARG_BOUNDARIES },
@ -1791,6 +1796,12 @@ static int parse_argv(int argc, char *argv[]) {
return log_error_errno(r, "Failed to parse --uid-max= value: %s", optarg);
break;
case ARG_UUID:
r = sd_id128_from_string(optarg, &arg_uuid);
if (r < 0)
return log_error_errno(r, "Failed to parse --uuid= value: %s", optarg);
break;
case 'z':
arg_fuzzy = true;
break;

View File

@ -149,6 +149,7 @@ static int vl_method_get_user_record(sd_varlink *link, sd_json_variant *paramete
{ "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 },
{ "uidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_min), 0 },
{ "uidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.uid_max), 0 },
{ "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(LookupParameters, match.uuid), 0 },
{}
};
@ -293,6 +294,7 @@ static int vl_method_get_group_record(sd_varlink *link, sd_json_variant *paramet
{ "dispositionMask", SD_JSON_VARIANT_ARRAY, json_dispatch_dispositions_mask, offsetof(LookupParameters, match.disposition_mask), 0 },
{ "gidMin", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_min), 0 },
{ "gidMax", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uid_gid, offsetof(LookupParameters, match.gid_max), 0 },
{ "uuid", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(LookupParameters, match.uuid), 0 },
{}
};

View File

@ -482,14 +482,34 @@ fi
# Decrypt/encrypt via varlink
echo '{"data":"Zm9vYmFyCg=="}' > /tmp/vlcredsdata
DATA="Zm9vYmFyCg=="
echo "{\"data\":\"$DATA\"}" > /tmp/vlcredsdata
varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Encrypt "$(cat /tmp/vlcredsdata)" | \
varlinkctl call --json=short /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt > /tmp/vlcredsdata2
cmp /tmp/vlcredsdata /tmp/vlcredsdata2
rm /tmp/vlcredsdata2
# Pick a key type explicitly
varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Encrypt "{\"data\":\"$DATA\",\"withKey\":\"host\"}" | \
varlinkctl call --json=short /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt > /tmp/vlcredsdata2
cmp /tmp/vlcredsdata /tmp/vlcredsdata2
rm /tmp/vlcredsdata2
varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Encrypt "{\"data\":\"$DATA\",\"withKey\":\"null\"}" | \
jq '.["allowNull"] = true' |
varlinkctl call --json=short /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt > /tmp/vlcredsdata2
cmp /tmp/vlcredsdata /tmp/vlcredsdata2
rm /tmp/vlcredsdata /tmp/vlcredsdata2
# Ensure allowNull works
(! varlinkctl call /run/systemd/io.systemd.Credentials io.systemd.Credentials.Encrypt "{\"data\":\"$DATA\",\"withKey\":\"null\"}" | \
jq '.["allowNull"] = false' |
varlinkctl call --json=short /run/systemd/io.systemd.Credentials io.systemd.Credentials.Decrypt )
clean_usertest() {
rm -f /tmp/usertest.data /tmp/usertest.data /tmp/brummbaer.data
}