various tweaks for systemd-creds & related (#38979)

This commit is contained in:
Yu Watanabe
2025-09-19 16:21:12 +09:00
committed by GitHub
6 changed files with 130 additions and 62 deletions
+7
View File
@@ -3915,6 +3915,13 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
<varname>LoadCredential=</varname> and <varname>LoadCredentialEncrypted=</varname> take priority over
credentials found by <varname>ImportCredential=</varname>.</para>
<para>Note that if decryption or authentication of a credential picked up as result of
<varname>ImportCredential=</varname> fails it will be skipped gracefully (a warning is generated, but
the credential will not be made available to the invoked service). This is different for those
configured via
<varname>SetCredentialEncrypted=</varname>/<varname>LoadCredentialEncrypted=</varname>, where failed
decryption/authentication will result in service failure.</para>
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
+22 -13
View File
@@ -463,7 +463,8 @@ static int maybe_decrypt_and_write_credential(
struct load_cred_args *args,
const char *id,
const char *data,
size_t size) {
size_t size,
bool graceful) {
_cleanup_(iovec_done_erase) struct iovec plaintext = {};
size_t add;
@@ -517,8 +518,14 @@ static int maybe_decrypt_and_write_credential(
default:
assert_not_reached();
}
if (r < 0)
if (r < 0) {
if (graceful) {
log_warning_errno(r, "Unable to decrypt credential '%s', skipping.", id);
return 0;
}
return r;
}
data = plaintext.iov_base;
size = plaintext.iov_len;
@@ -607,7 +614,7 @@ static int load_credential_glob(
if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m", *p);
r = maybe_decrypt_and_write_credential(args, fn, data, size);
r = maybe_decrypt_and_write_credential(args, fn, data, size, /* graceful= */ true);
if (r < 0)
return r;
}
@@ -732,7 +739,7 @@ static int load_credential(
if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m", path);
return maybe_decrypt_and_write_credential(args, id, data, size);
return maybe_decrypt_and_write_credential(args, id, data, size, /* graceful= */ true);
}
static int load_cred_recurse_dir_cb(
@@ -869,10 +876,11 @@ static int acquire_credentials(
args.encrypted = false;
r = load_credential_glob(&args,
ic,
search_path,
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER);
r = load_credential_glob(
&args,
ic,
search_path,
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER);
if (r < 0)
return r;
@@ -884,10 +892,11 @@ static int acquire_credentials(
args.encrypted = true;
r = load_credential_glob(&args,
ic,
search_path,
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64);
r = load_credential_glob(
&args,
ic,
search_path,
READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER|READ_FULL_FILE_UNBASE64);
if (r < 0)
return r;
}
@@ -905,7 +914,7 @@ static int acquire_credentials(
if (errno != ENOENT)
return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
r = maybe_decrypt_and_write_credential(&args, sc->id, sc->data, sc->size);
r = maybe_decrypt_and_write_credential(&args, sc->id, sc->data, sc->size, /* graceful= */ false);
if (r < 0)
return r;
}
+45 -33
View File
@@ -1411,7 +1411,7 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
if (r == -EBADMSG)
return sd_varlink_error(link, "io.systemd.Credentials.BadFormat", NULL);
if (r == -EREMOTE)
if (r == -EDESTADDRREQ)
return sd_varlink_error(link, "io.systemd.Credentials.NameMismatch", NULL);
if (r == -ESTALE)
return sd_varlink_error(link, "io.systemd.Credentials.TimeMismatch", NULL);
@@ -1419,6 +1419,16 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
return sd_varlink_error(link, "io.systemd.Credentials.NoSuchUser", NULL);
if (r == -EMEDIUMTYPE)
return sd_varlink_error(link, "io.systemd.Credentials.BadScope", NULL);
if (r == -EHOSTDOWN)
return sd_varlink_error(link, "io.systemd.Credentials.CantFindPCRSignature", NULL);
if (r == -EHWPOISON)
return sd_varlink_error(link, "io.systemd.Credentials.NullKeyNotAllowed", NULL);
if (r == -EREMOTE)
return sd_varlink_error(link, "io.systemd.Credentials.KeyBelongsToOtherTPM", NULL);
if (r == -ENOLCK)
return sd_varlink_error(link, "io.systemd.Credentials.TPMInDictionaryLockout", NULL);
if (IN_SET(r, -EREMCHG, -ENOANO, -EUCLEAN, -EPERM))
return sd_varlink_error(link, "io.systemd.Credentials.UnexpectedPCRState", NULL);
if (r < 0)
return r;
@@ -1433,6 +1443,38 @@ static int vl_method_decrypt(sd_varlink *link, sd_json_variant *parameters, sd_v
return sd_varlink_reply(link, reply);
}
static int vl_server(void) {
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
_cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
int r;
/* Invocation as Varlink service */
r = varlink_server_new(
&varlink_server,
SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE,
&polkit_registry);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials);
if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m");
r = sd_varlink_server_bind_method_many(
varlink_server,
"io.systemd.Credentials.Encrypt", vl_method_encrypt,
"io.systemd.Credentials.Decrypt", vl_method_decrypt);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
r = sd_varlink_server_loop_auto(varlink_server);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
return 0;
}
static int run(int argc, char *argv[]) {
int r;
@@ -1442,38 +1484,8 @@ static int run(int argc, char *argv[]) {
if (r <= 0)
return r;
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_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA|SD_VARLINK_SERVER_INPUT_SENSITIVE,
NULL);
if (r < 0)
return log_error_errno(r, "Failed to allocate Varlink server: %m");
r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials);
if (r < 0)
return log_error_errno(r, "Failed to add Varlink interface: %m");
r = sd_varlink_server_bind_method_many(
varlink_server,
"io.systemd.Credentials.Encrypt", vl_method_encrypt,
"io.systemd.Credentials.Decrypt", vl_method_decrypt);
if (r < 0)
return log_error_errno(r, "Failed to bind Varlink methods: %m");
sd_varlink_server_set_userdata(varlink_server, &polkit_registry);
r = sd_varlink_server_loop_auto(varlink_server);
if (r < 0)
return log_error_errno(r, "Failed to run Varlink event loop: %m");
return 0;
}
if (arg_varlink)
return vl_server();
return creds_main(argc, argv);
}
+39 -14
View File
@@ -1201,6 +1201,19 @@ int decrypt_credential_and_warn(
assert(iovec_is_valid(input));
assert(ret);
/* 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
* -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 valdity has passed
* -ESRCH → User specified for scope does not exist on this system
*
* (plus the various error codes tpm2_unseal() returns) */
h = (struct encrypted_credential_header*) input->iov_base;
/* The ID must fit in, for the current and all future formats */
@@ -1218,8 +1231,10 @@ int decrypt_credential_and_warn(
if (with_tpm2_pk) {
r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json);
if (r == -ENOENT)
return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Couldn't find PCR signature file: %m");
if (r < 0)
return log_error_errno(r, "Failed to load pcr signature: %m");
return log_error_errno(r, "Failed to load PCR signature: %m");
}
if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) {
@@ -1234,7 +1249,7 @@ int decrypt_credential_and_warn(
if (efi_has_tpm2()) {
if (is_efi_secure_boot())
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
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.");
log_warning("Credential uses fixed key for use when TPM2 is absent, but TPM2 is present! Accepting anyway, since SecureBoot is disabled.");
@@ -1354,7 +1369,7 @@ int decrypt_credential_and_warn(
/* srk= */ NULL,
&tpm2_key);
if (r == -EREMOTE)
return log_error_errno(r, "TPM key integrity check failed. Key enrolled in superblock most likely does not belong to this TPM.");
return log_error_errno(r, "TPM key integrity check failed. Key most likely does not belong to this TPM.");
if (ERRNO_IS_NEG_TPM2_UNSEAL_BAD_PCR(r))
return log_error_errno(r, "TPM policy does not match current system state. Either system has been tempered with or policy out-of-date: %m");
if (r < 0)
@@ -1486,7 +1501,7 @@ int decrypt_credential_and_warn(
if (r < 0 && r != -ENXIO)
log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NAME: %m");
if (r != 0)
return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Embedded credential name '%s' does not match filename '%s', refusing.", embedded_name, validate_name);
return log_error_errno(SYNTHETIC_ERRNO(EDESTADDRREQ), "Embedded credential name '%s' does not match filename '%s', refusing.", embedded_name, validate_name);
log_debug("Embedded credential name '%s' does not match expected name '%s', but configured to use credential anyway.", embedded_name, validate_name);
}
@@ -1637,16 +1652,26 @@ int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp,
if (r < 0)
return log_error_errno(r, "Failed to call Decrypt() varlink call.");
if (!isempty(error_id)) {
if (streq(error_id, "io.systemd.Credentials.BadFormat"))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Bad credential format.");
if (streq(error_id, "io.systemd.Credentials.NameMismatch"))
return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Name in credential doesn't match expectations.");
if (streq(error_id, "io.systemd.Credentials.TimeMismatch"))
return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Outside of credential validity time window.");
if (streq(error_id, "io.systemd.Credentials.NoSuchUser"))
return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user.");
if (streq(error_id, "io.systemd.Credentials.BadScope"))
return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Scope mismtach.");
static struct {
const char *id;
int errnum;
const char *msg;
} table[] = {
{ "io.systemd.Credentials.BadFormat", EBADMSG, "Bad credential format." },
{ "io.systemd.Credentials.NameMismatch", EDESTADDRREQ, "Name in credential doesn't match expectations." },
{ "io.systemd.Credentials.TimeMismatch", ESTALE, "Outside of credential validity time window." },
{ "io.systemd.Credentials.NoSuchUser", ESRCH, "No such user." },
{ "io.systemd.Credentials.BadScope", EMEDIUMTYPE, "Scope mismatch." },
{ "io.systemd.Credentials.CantFindPCRSignature", EHOSTDOWN, "PCR signature required for decryption, but could not be found." },
{ "io.systemd.Credentials.NullKeyNotAllowed", EHWPOISON, "The key was encrypted with a null key, but that's now allowed during decryption." },
{ "io.systemd.Credentials.KeyBelongsToOtherTPM", EREMOTE, "The TPM integrity check for this key failed, key probably belongs to another TPM, or was corrupted." },
{ "io.systemd.Credentials.TPMInDictionaryLockout", ENOLCK, "The TPM is in dictionary lockout mode, cannot operate." },
{ "io.systemd.Credentials.UnexpectedPCRState" , EUCLEAN, "Unexpected TPM PCR state of the system." },
};
FOREACH_ELEMENT(i, table)
if (streq(i->id, error_id))
return log_error_errno(SYNTHETIC_ERRNO(i->errnum), "%s", i->msg);
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to decrypt: %s", error_id);
}
+16 -1
View File
@@ -44,6 +44,11 @@ static SD_VARLINK_DEFINE_ERROR(NameMismatch);
static SD_VARLINK_DEFINE_ERROR(TimeMismatch);
static SD_VARLINK_DEFINE_ERROR(NoSuchUser);
static SD_VARLINK_DEFINE_ERROR(BadScope);
static SD_VARLINK_DEFINE_ERROR(CantFindPCRSignature);
static SD_VARLINK_DEFINE_ERROR(NullKeyNotAllowed);
static SD_VARLINK_DEFINE_ERROR(KeyBelongsToOtherTPM);
static SD_VARLINK_DEFINE_ERROR(TPMInDictionaryLockout);
static SD_VARLINK_DEFINE_ERROR(UnexpectedPCRState);
SD_VARLINK_DEFINE_INTERFACE(
io_systemd_Credentials,
@@ -62,4 +67,14 @@ SD_VARLINK_DEFINE_INTERFACE(
SD_VARLINK_SYMBOL_COMMENT("The specified user does not exist."),
&vl_error_NoSuchUser,
SD_VARLINK_SYMBOL_COMMENT("The credential does not match the selected scope."),
&vl_error_BadScope);
&vl_error_BadScope,
SD_VARLINK_SYMBOL_COMMENT("PCR signature required for decryption, but not found."),
&vl_error_CantFindPCRSignature,
SD_VARLINK_SYMBOL_COMMENT("The key was encrypted with a null key, but that's now allowed during decryption."),
&vl_error_NullKeyNotAllowed,
SD_VARLINK_SYMBOL_COMMENT("The TPM integrity check for this key failed, key probably belongs to another TPM, or was corrupted."),
&vl_error_KeyBelongsToOtherTPM,
SD_VARLINK_SYMBOL_COMMENT("The TPM is in dictionary lockout mode, cannot operate."),
&vl_error_TPMInDictionaryLockout,
SD_VARLINK_SYMBOL_COMMENT("Unexpected TPM PCR state of the system."),
&vl_error_UnexpectedPCRState);
+1 -1
View File
@@ -164,7 +164,7 @@ static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) {
&encrypted,
CREDENTIAL_ALLOW_NULL,
&decrypted);
ASSERT_ERROR(r, EREMOTE); /* name didn't match */
ASSERT_ERROR(r, EDESTADDRREQ); /* name didn't match */
r = decrypt_credential_and_warn(
"foo",