mirror of
https://github.com/systemd/systemd-stable.git
synced 2025-03-06 12:58:22 +03:00
core: teach LoadCredential= to load from a directory
This commit is contained in:
parent
7e1f61137a
commit
3989bdc1ad
3
TODO
3
TODO
@ -303,9 +303,6 @@ Features:
|
||||
- make gatwayd/remote read key via creds logic
|
||||
- add sd_notify() command for flushing out creds not needed anymore
|
||||
|
||||
* teach LoadCredential= the ability to load all files from a specified dir as
|
||||
individual creds
|
||||
|
||||
* add tpm.target or so which is delayed until TPM2 device showed up in case
|
||||
firmware indicates there is one.
|
||||
|
||||
|
@ -3007,7 +3007,10 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
newline characters and <constant>NUL</constant> bytes. If the file system path is omitted it is
|
||||
chosen identical to the credential name, i.e. this is a terse way do declare credentials to inherit
|
||||
from the service manager into a service. This option may be used multiple times, each time defining
|
||||
an additional credential to pass to the unit.</para>
|
||||
an additional credential to pass to the unit. Alternatively, if the path is a directory, every file
|
||||
in that directory will be loaded as a separate credential. The ID for each credential will be the
|
||||
provided ID suffixed with <literal>_$FILENAME</literal> (e.g., <literal>Key_file1</literal>). When
|
||||
loading from a directory, symlinks will be ignored.</para>
|
||||
|
||||
<para>The <varname>LoadCredentialEncrypted=</varname> setting is identical to
|
||||
<varname>LoadCredential=</varname>, except that the credential data is decrypted before being passed
|
||||
|
@ -80,6 +80,7 @@
|
||||
#include "path-util.h"
|
||||
#include "process-util.h"
|
||||
#include "random-util.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "rm-rf.h"
|
||||
#if HAVE_SECCOMP
|
||||
@ -2613,6 +2614,168 @@ static int write_credential(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_credential(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
ExecLoadCredential *lc,
|
||||
const char *unit,
|
||||
int read_dfd,
|
||||
int write_dfd,
|
||||
uid_t uid,
|
||||
bool ownership_ok,
|
||||
uint64_t *left) {
|
||||
|
||||
assert(context);
|
||||
assert(lc);
|
||||
assert(unit);
|
||||
assert(write_dfd >= 0);
|
||||
assert(left);
|
||||
|
||||
ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
|
||||
_cleanup_(erase_and_freep) char *data = NULL;
|
||||
_cleanup_free_ char *j = NULL, *bindname = NULL;
|
||||
bool missing_ok = true;
|
||||
const char *source;
|
||||
size_t size, add;
|
||||
int r;
|
||||
|
||||
if (path_is_absolute(lc->path) || read_dfd >= 0) {
|
||||
/* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
|
||||
source = lc->path;
|
||||
flags |= READ_FULL_FILE_CONNECT_SOCKET;
|
||||
|
||||
/* Pass some minimal info about the unit and the credential name we are looking to acquire
|
||||
* via the source socket address in case we read off an AF_UNIX socket. */
|
||||
if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, lc->id) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
missing_ok = false;
|
||||
|
||||
} else if (params->received_credentials) {
|
||||
/* If this is a relative path, take it relative to the credentials we received
|
||||
* ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
|
||||
* on a credential store, i.e. this is guaranteed to be regular files. */
|
||||
j = path_join(params->received_credentials, lc->path);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
source = j;
|
||||
} else
|
||||
source = NULL;
|
||||
|
||||
if (source)
|
||||
r = read_full_file_full(
|
||||
read_dfd, source,
|
||||
UINT64_MAX,
|
||||
lc->encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
|
||||
flags | (lc->encrypted ? READ_FULL_FILE_UNBASE64 : 0),
|
||||
bindname,
|
||||
&data, &size);
|
||||
else
|
||||
r = -ENOENT;
|
||||
|
||||
if (r == -ENOENT && (missing_ok || hashmap_contains(context->set_credentials, lc->id))) {
|
||||
/* Make a missing inherited credential non-fatal, let's just continue. After all apps
|
||||
* will get clear errors if we don't pass such a missing credential on as they
|
||||
* themselves will get ENOENT when trying to read them, which should not be much
|
||||
* worse than when we handle the error here and make it fatal.
|
||||
*
|
||||
* Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
|
||||
* we are fine, too. */
|
||||
log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", lc->path);
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read credential '%s': %m", lc->path);
|
||||
|
||||
if (lc->encrypted) {
|
||||
_cleanup_free_ void *plaintext = NULL;
|
||||
size_t plaintext_size = 0;
|
||||
|
||||
r = decrypt_credential_and_warn(lc->id, now(CLOCK_REALTIME), NULL, data, size, &plaintext, &plaintext_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(data, plaintext);
|
||||
size = plaintext_size;
|
||||
}
|
||||
|
||||
add = strlen(lc->id) + size;
|
||||
if (add > *left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(write_dfd, lc->id, data, size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*left -= add;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct load_cred_args {
|
||||
Set *seen_creds;
|
||||
|
||||
const ExecContext *context;
|
||||
const ExecParameters *params;
|
||||
ExecLoadCredential *parent_local_credential;
|
||||
const char *unit;
|
||||
int dfd;
|
||||
uid_t uid;
|
||||
bool ownership_ok;
|
||||
uint64_t *left;
|
||||
};
|
||||
|
||||
static int load_cred_recurse_dir_cb(
|
||||
RecurseDirEvent event,
|
||||
const char *path,
|
||||
int dir_fd,
|
||||
int inode_fd,
|
||||
const struct dirent *de,
|
||||
const struct statx *sx,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_free_ char *credname = NULL, *sub_id = NULL;
|
||||
struct load_cred_args *args = userdata;
|
||||
int r;
|
||||
|
||||
if (event != RECURSE_DIR_ENTRY)
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
|
||||
if (!IN_SET(de->d_type, DT_REG, DT_SOCK))
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
|
||||
credname = strreplace(path, "/", "_");
|
||||
if (!credname)
|
||||
return -ENOMEM;
|
||||
|
||||
sub_id = strjoin(args->parent_local_credential->id, "_", credname);
|
||||
if (!sub_id)
|
||||
return -ENOMEM;
|
||||
|
||||
if (!credential_name_valid(sub_id))
|
||||
return -EINVAL;
|
||||
|
||||
if (set_contains(args->seen_creds, sub_id)) {
|
||||
log_debug("Skipping credential with duplicated ID %s at %s", sub_id, path);
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
}
|
||||
|
||||
r = set_put_strdup(&args->seen_creds, sub_id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = load_credential(args->context, args->params,
|
||||
&(ExecLoadCredential) {
|
||||
.id = sub_id,
|
||||
.path = (char *) de->d_name,
|
||||
.encrypted = args->parent_local_credential->encrypted,
|
||||
}, args->unit, dir_fd, args->dfd, args->uid, args->ownership_ok, args->left);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return RECURSE_DIR_CONTINUE;
|
||||
}
|
||||
|
||||
static int acquire_credentials(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
@ -2623,6 +2786,7 @@ static int acquire_credentials(
|
||||
|
||||
uint64_t left = CREDENTIALS_TOTAL_SIZE_MAX;
|
||||
_cleanup_close_ int dfd = -1;
|
||||
_cleanup_set_free_ Set *seen_creds = NULL;
|
||||
ExecLoadCredential *lc;
|
||||
ExecSetCredential *sc;
|
||||
int r;
|
||||
@ -2634,84 +2798,53 @@ static int acquire_credentials(
|
||||
if (dfd < 0)
|
||||
return -errno;
|
||||
|
||||
seen_creds = set_new(&string_hash_ops_free);
|
||||
if (!seen_creds)
|
||||
return -ENOMEM;
|
||||
|
||||
/* First, load credentials off disk (or acquire via AF_UNIX socket) */
|
||||
HASHMAP_FOREACH(lc, context->load_credentials) {
|
||||
ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
|
||||
_cleanup_(erase_and_freep) char *data = NULL;
|
||||
_cleanup_free_ char *j = NULL, *bindname = NULL;
|
||||
bool missing_ok = true;
|
||||
const char *source;
|
||||
size_t size, add;
|
||||
_cleanup_close_ int sub_fd = -1;
|
||||
|
||||
if (path_is_absolute(lc->path)) {
|
||||
/* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
|
||||
source = lc->path;
|
||||
flags |= READ_FULL_FILE_CONNECT_SOCKET;
|
||||
|
||||
/* Pass some minimal info about the unit and the credential name we are looking to acquire
|
||||
* via the source socket address in case we read off an AF_UNIX socket. */
|
||||
if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, lc->id) < 0)
|
||||
return -ENOMEM;
|
||||
|
||||
missing_ok = false;
|
||||
|
||||
} else if (params->received_credentials) {
|
||||
/* If this is a relative path, take it relative to the credentials we received
|
||||
* ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
|
||||
* on a credential store, i.e. this is guaranteed to be regular files. */
|
||||
j = path_join(params->received_credentials, lc->path);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
source = j;
|
||||
} else
|
||||
source = NULL;
|
||||
|
||||
if (source)
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, source,
|
||||
UINT64_MAX,
|
||||
lc->encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
|
||||
flags | (lc->encrypted ? READ_FULL_FILE_UNBASE64 : 0),
|
||||
bindname,
|
||||
&data, &size);
|
||||
else
|
||||
r = -ENOENT;
|
||||
if (r == -ENOENT && (missing_ok || hashmap_contains(context->set_credentials, lc->id))) {
|
||||
/* Make a missing inherited credential non-fatal, let's just continue. After all apps
|
||||
* will get clear errors if we don't pass such a missing credential on as they
|
||||
* themselves will get ENOENT when trying to read them, which should not be much
|
||||
* worse than when we handle the error here and make it fatal.
|
||||
*
|
||||
* Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
|
||||
* we are fine, too. */
|
||||
log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", lc->path);
|
||||
/* Skip over credentials with unspecified paths. These are received by the
|
||||
* service manager via the $CREDENTIALS_DIRECTORY environment variable. */
|
||||
if (!is_path(lc->path) && streq(lc->id, lc->path))
|
||||
continue;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_debug_errno(r, "Failed to read credential '%s': %m", lc->path);
|
||||
|
||||
if (lc->encrypted) {
|
||||
_cleanup_free_ void *plaintext = NULL;
|
||||
size_t plaintext_size = 0;
|
||||
sub_fd = open(lc->path, O_DIRECTORY|O_CLOEXEC|O_RDONLY);
|
||||
if (sub_fd < 0 && errno != ENOTDIR)
|
||||
return -errno;
|
||||
|
||||
r = decrypt_credential_and_warn(lc->id, now(CLOCK_REALTIME), NULL, data, size, &plaintext, &plaintext_size);
|
||||
if (sub_fd < 0) {
|
||||
r = set_put_strdup(&seen_creds, lc->id);
|
||||
if (r < 0)
|
||||
return r;
|
||||
r = load_credential(context, params, lc, unit, -1, dfd, uid, ownership_ok, &left);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
free_and_replace(data, plaintext);
|
||||
size = plaintext_size;
|
||||
} else {
|
||||
r = recurse_dir(
|
||||
sub_fd,
|
||||
/* path= */ "",
|
||||
/* statx_mask= */ 0,
|
||||
/* n_depth_max= */ UINT_MAX,
|
||||
RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE,
|
||||
load_cred_recurse_dir_cb,
|
||||
&(struct load_cred_args) {
|
||||
.seen_creds = seen_creds,
|
||||
.context = context,
|
||||
.params = params,
|
||||
.parent_local_credential = lc,
|
||||
.unit = unit,
|
||||
.dfd = dfd,
|
||||
.uid = uid,
|
||||
.ownership_ok = ownership_ok,
|
||||
.left = &left,
|
||||
});
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
add = strlen(lc->id) + size;
|
||||
if (add > left)
|
||||
return -E2BIG;
|
||||
|
||||
r = write_credential(dfd, lc->id, data, size, uid, ownership_ok);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
left -= add;
|
||||
}
|
||||
|
||||
/* First we use the literally specified credentials. Note that they might be overridden again below,
|
||||
|
@ -28,8 +28,25 @@ systemd-run -p LoadCredential=passwd:/etc/passwd \
|
||||
rm '${CREDENTIALS_DIRECTORY}/passwd' \
|
||||
&& { echo 'unexpected success'; exit 1; }
|
||||
|
||||
# Now test encrypted credentials (only supported when built with OpenSSL though)
|
||||
# Check directory-based loading
|
||||
mkdir -p /tmp/ts54-creds/sub
|
||||
echo -n a >/tmp/ts54-creds/foo
|
||||
echo -n b >/tmp/ts54-creds/bar
|
||||
echo -n c >/tmp/ts54-creds/baz
|
||||
echo -n d >/tmp/ts54-creds/sub/qux
|
||||
systemd-run -p LoadCredential=cred:/tmp/ts54-creds \
|
||||
-p DynamicUser=1 \
|
||||
--wait \
|
||||
--pipe \
|
||||
cat '${CREDENTIALS_DIRECTORY}/cred_foo' \
|
||||
'${CREDENTIALS_DIRECTORY}/cred_bar' \
|
||||
'${CREDENTIALS_DIRECTORY}/cred_baz' \
|
||||
'${CREDENTIALS_DIRECTORY}/cred_sub_qux' >/tmp/ts54-concat
|
||||
( echo -n abcd ) | cmp /tmp/ts54-concat
|
||||
rm /tmp/ts54-concat
|
||||
rm -rf /tmp/ts54-creds
|
||||
|
||||
# Now test encrypted credentials (only supported when built with OpenSSL though)
|
||||
if systemctl --version | grep -q -- +OPENSSL ; then
|
||||
echo -n $RANDOM >/tmp/test-54-plaintext
|
||||
systemd-creds encrypt --name=test-54 /tmp/test-54-plaintext /tmp/test-54-ciphertext
|
||||
|
Loading…
x
Reference in New Issue
Block a user