credential plus a file system path. The ID must be a short ASCII string suitable as filename in the
filesystem, and may be chosen freely by the user. If the specified path is absolute it is opened as
regular file and the credential data is read from it. If the absolute path refers to an
- <constant>AF_UNIX</constant> stream socket in the file system a connection is made to it and the
- credential data read from the connection, providing an easy IPC integration point for dynamically
- providing credentials from other services. If the specified path is not absolute and itself qualifies
- as valid credential identifier it is understood to refer to a credential that the service manager
- itself received via the <varname>$CREDENTIALS_DIRECTORY</varname> environment variable, which may be
- used to propagate credentials from an invoking environment (e.g. a container manager that invoked the
- service manager) into a service. The contents of the file/socket may be arbitrary binary or textual
- data, including newline characters and NUL bytes. This option may be used multiple times, each time
- defining an additional credential to pass to the unit.</para>
+ <constant>AF_UNIX</constant> stream socket in the file system a connection is made to it (only once
+ at unit start-up) and the credential data read from the connection, providing an easy IPC integration
+ point for dynamically providing credentials from other services. If the specified path is not
+ absolute and itself qualifies as valid credential identifier it is understood to refer to a
+ credential that the service manager itself received via the <varname>$CREDENTIALS_DIRECTORY</varname>
+ environment variable, which may be used to propagate credentials from an invoking environment (e.g. a
+ container manager that invoked the service manager) into a service. The contents of the file/socket
+ may be arbitrary binary or textual data, including newline characters and <constant>NUL</constant>
+ bytes. This option may be used multiple times, each time defining an additional credential to pass to
+ the unit.</para>
<para>The credential files/IPC sockets must be accessible to the service manager, but don't have to
be directly accessible to the unit's processes: the credential data is read and copied into separate,
e.g. <literal>ExecStart=cat ${CREDENTIALS_DIRECTORY}/mycred</literal>.</para>
<para>Currently, an accumulated credential size limit of 1M bytes per unit is
- enforced.</para></listitem>
+ enforced.</para>
+
+ <para>If referencing an <constant>AF_UNIX</constant> stream socket to connect to, the connection will
+ originate from an abstract namespace socket, that includes information about the unit and the
+ credential ID in its socket name. Use <citerefentry
+ project='man-pages'><refentrytitle>getpeername</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+ to query this information. The returned socket name is formatted as <constant>NUL</constant>
+ <replaceable>RANDOM</replaceable> <literal>/unit/</literal> <replaceable>UNIT</replaceable>
+ <literal>/</literal> <replaceable>ID</replaceable>, i.e. a <constant>NUL</constant> byte (as required
+ for abstract namespace socket names), followed by a random string (consisting of alphadecimal
+ characters), followed by the literal string <literal>/unit/</literal>, followed by the requesting
+ unit name, followed by the literal character <literal>/</literal>, followed by the textual credential
+ ID requested. Example: <literal>\0adf9d86b6eda275e/unit/foobar.service/credx</literal> in case the
+ credential <literal>credx</literal> is requested for a unit <literal>foobar.service</literal>. This
+ functionality is useful for using a single listening socket to serve credentials to multiple
+ consumers.</para></listitem>
</varlistentry>
<varlistentry>
return r;
}
-int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size) {
+int read_full_file_full(
+ int dir_fd,
+ const char *filename,
+ ReadFullFileFlags flags,
+ const char *bind_name,
+ char **contents, size_t *size) {
+
_cleanup_fclose_ FILE *f = NULL;
int r;
if (sk < 0)
return -errno;
+ if (bind_name) {
+ /* If the caller specified a socket name to bind to, do so before connecting. This is
+ * useful to communicate some minor, short meta-information token from the client to
+ * the server. */
+ union sockaddr_union bsa;
+
+ r = sockaddr_un_set_path(&bsa.un, bind_name);
+ if (r < 0)
+ return r;
+
+ if (bind(sk, &bsa.sa, r) < 0)
+ return r;
+ }
+
if (connect(sk, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
return errno == ENOTSOCK ? -ENXIO : -errno; /* propagate original error if this is
* not a socket after all */
int write_string_filef(const char *fn, WriteStringFileFlags flags, const char *format, ...) _printf_(3, 4);
int read_one_line_file(const char *filename, char **line);
-int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size);
+int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flags, const char *bind_name, char **contents, size_t *size);
static inline int read_full_file(const char *filename, char **contents, size_t *size) {
- return read_full_file_full(AT_FDCWD, filename, 0, contents, size);
+ return read_full_file_full(AT_FDCWD, filename, 0, NULL, contents, size);
}
int read_full_virtual_file(const char *filename, char **ret_contents, size_t *ret_size);
int read_full_stream_full(FILE *f, const char *filename, ReadFullFileFlags flags, char **contents, size_t *size);
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
+#include "random-util.h"
#include "rlimit-util.h"
#include "rm-rf.h"
#if HAVE_SECCOMP
static int acquire_credentials(
const ExecContext *context,
const ExecParameters *params,
+ const char *unit,
const char *p,
uid_t uid,
bool ownership_ok) {
STRV_FOREACH_PAIR(id, fn, context->load_credentials) {
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
_cleanup_(erase_and_freep) char *data = NULL;
- _cleanup_free_ char *j = NULL;
+ _cleanup_free_ char *j = NULL, *bindname = NULL;
const char *source;
size_t size, add;
/* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
source = *fn;
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, *id) < 0)
+ return -ENOMEM;
+
} 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
} else
source = NULL;
+
if (source)
- r = read_full_file_full(AT_FDCWD, source, flags, &data, &size);
+ r = read_full_file_full(AT_FDCWD, source, flags, bindname, &data, &size);
else
r = -ENOENT;
if (r == -ENOENT &&
static int setup_credentials_internal(
const ExecContext *context,
const ExecParameters *params,
+ const char *unit,
const char *final, /* This is where the credential store shall eventually end up at */
const char *workspace, /* This is where we can prepare it before moving it to the final place */
bool reuse_workspace, /* Whether to reuse any existing workspace mount if it already is a mount */
assert(!must_mount || workspace_mounted > 0);
where = workspace_mounted ? workspace : final;
- r = acquire_credentials(context, params, where, uid, workspace_mounted);
+ r = acquire_credentials(context, params, unit, where, uid, workspace_mounted);
if (r < 0)
return r;
r = setup_credentials_internal(
context,
params,
+ unit,
p, /* final mount point */
u, /* temporary workspace to overmount */
true, /* reuse the workspace if it is already a mount */
r = setup_credentials_internal(
context,
params,
+ unit,
p, /* final mount point */
"/dev/shm", /* temporary workspace to overmount */
false, /* do not reuse /dev/shm if it is already a mount, under no circumstances */
if (arg_key_pem)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Key file specified twice");
- r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, &arg_key_pem, NULL);
+ r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_key_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read key file: %m");
assert(arg_key_pem);
if (arg_cert_pem)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Certificate file specified twice");
- r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, &arg_cert_pem, NULL);
+ r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_cert_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read certificate file: %m");
assert(arg_cert_pem);
if (arg_trust_pem)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"CA certificate file specified twice");
- r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, &arg_trust_pem, NULL);
+ r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_CONNECT_SOCKET, NULL, &arg_trust_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read CA certificate file: %m");
assert(arg_trust_pem);
static int load_certificates(char **key, char **cert, char **trust) {
int r;
- r = read_full_file_full(AT_FDCWD, arg_key ?: PRIV_KEY_FILE, READ_FULL_FILE_CONNECT_SOCKET, key, NULL);
+ r = read_full_file_full(AT_FDCWD, arg_key ?: PRIV_KEY_FILE, READ_FULL_FILE_CONNECT_SOCKET, NULL, key, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read key from file '%s': %m",
arg_key ?: PRIV_KEY_FILE);
- r = read_full_file_full(AT_FDCWD, arg_cert ?: CERT_FILE, READ_FULL_FILE_CONNECT_SOCKET, cert, NULL);
+ r = read_full_file_full(AT_FDCWD, arg_cert ?: CERT_FILE, READ_FULL_FILE_CONNECT_SOCKET, NULL, cert, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read certificate from file '%s': %m",
arg_cert ?: CERT_FILE);
if (arg_trust_all)
log_info("Certificate checking disabled.");
else {
- r = read_full_file_full(AT_FDCWD, arg_trust ?: TRUST_FILE, READ_FULL_FILE_CONNECT_SOCKET, trust, NULL);
+ r = read_full_file_full(AT_FDCWD, arg_trust ?: TRUST_FILE, READ_FULL_FILE_CONNECT_SOCKET, NULL, trust, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read CA certificate file '%s': %m",
arg_trust ?: TRUST_FILE);
r = read_full_file_full(
AT_FDCWD, sa->key_file,
READ_FULL_FILE_SECURE | READ_FULL_FILE_UNHEX | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET,
- (char **) &key, &key_len);
+ NULL, (char **) &key, &key_len);
if (r < 0)
return log_netdev_error_errno(netdev, r,
"Failed to read key from '%s', ignoring: %m",
r = read_full_file_full(
AT_FDCWD, filename,
READ_FULL_FILE_SECURE | READ_FULL_FILE_UNBASE64 | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET,
- &key, &key_len);
+ NULL, &key, &key_len);
if (r < 0)
return r;
return log_oom();
}
- r = read_full_file_full(AT_FDCWD, j ?: p, flags, &data, &size);
+ r = read_full_file_full(AT_FDCWD, j ?: p, flags, NULL, &data, &size);
if (r < 0)
return log_error_errno(r, "Failed to read credential '%s': %m", j ?: p);
_cleanup_(erase_and_freep) char *k = NULL;
size_t n = 0;
- r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_SECURE|READ_FULL_FILE_CONNECT_SOCKET, &k, &n);
+ r = read_full_file_full(AT_FDCWD, optarg, READ_FULL_FILE_SECURE|READ_FULL_FILE_CONNECT_SOCKET, NULL, &k, &n);
if (r < 0)
return log_error_errno(r, "Failed to read key file '%s': %m", optarg);
if (verity->root_hash && !verity->root_hash_sig) {
if (root_hash_sig_path) {
- r = read_full_file_full(AT_FDCWD, root_hash_sig_path, 0, (char**) &root_hash_sig, &root_hash_sig_size);
+ r = read_full_file_full(AT_FDCWD, root_hash_sig_path, 0, NULL, (char**) &root_hash_sig, &root_hash_sig_size);
if (r < 0 && r != -ENOENT)
return r;
if (!p)
return -ENOMEM;
- r = read_full_file_full(AT_FDCWD, p, 0, (char**) &root_hash_sig, &root_hash_sig_size);
+ r = read_full_file_full(AT_FDCWD, p, 0, NULL, (char**) &root_hash_sig, &root_hash_sig_size);
if (r < 0 && r != -ENOENT)
return r;
if (r >= 0)
if (!p)
return -ENOMEM;
- r = read_full_file_full(AT_FDCWD, p, 0, (char**) &root_hash_sig, &root_hash_sig_size);
+ r = read_full_file_full(AT_FDCWD, p, 0, NULL, (char**) &root_hash_sig, &root_hash_sig_size);
if (r < 0 && r != -ENOENT)
return r;
if (r >= 0)
if (f)
r = read_full_stream(f, &text, NULL);
else if (path)
- r = read_full_file_full(dir_fd, path, 0, &text, NULL);
+ r = read_full_file_full(dir_fd, path, 0, NULL, &text, NULL);
else
return -EINVAL;
if (r < 0)
#include "io-util.h"
#include "parse-util.h"
#include "process-util.h"
+#include "random-util.h"
#include "rm-rf.h"
#include "socket-util.h"
#include "string-util.h"
static void test_read_full_file_socket(void) {
_cleanup_(rm_rf_physical_and_freep) char *z = NULL;
_cleanup_close_ int listener = -1;
- _cleanup_free_ char *data = NULL;
+ _cleanup_free_ char *data = NULL, *clientname = NULL;
union sockaddr_union sa;
const char *j;
size_t size;
assert_se(bind(listener, &sa.sa, SOCKADDR_UN_LEN(sa.un)) >= 0);
assert_se(listen(listener, 1) >= 0);
+ /* Bind the *client* socket to some randomized name, to verify that this works correctly. */
+ assert_se(asprintf(&clientname, "@%" PRIx64 "/test-bindname", random_u64()) >= 0);
+
r = safe_fork("(server)", FORK_DEATHSIG|FORK_LOG, &pid);
assert_se(r >= 0);
if (r == 0) {
+ union sockaddr_union peer = {};
+ socklen_t peerlen = sizeof(peer);
_cleanup_close_ int rfd = -1;
/* child */
rfd = accept4(listener, NULL, 0, SOCK_CLOEXEC);
assert_se(rfd >= 0);
+ assert_se(getpeername(rfd, &peer.sa, &peerlen) >= 0);
+
+ assert_se(peer.un.sun_family == AF_UNIX);
+ assert_se(peerlen > offsetof(struct sockaddr_un, sun_path));
+ assert_se(peer.un.sun_path[0] == 0);
+ assert_se(streq(peer.un.sun_path + 1, clientname + 1));
+
#define TEST_STR "This is a test\nreally."
assert_se(write(rfd, TEST_STR, strlen(TEST_STR)) == strlen(TEST_STR));
_exit(EXIT_SUCCESS);
}
- assert_se(read_full_file_full(AT_FDCWD, j, 0, &data, &size) == -ENXIO);
- assert_se(read_full_file_full(AT_FDCWD, j, READ_FULL_FILE_CONNECT_SOCKET, &data, &size) >= 0);
+ assert_se(read_full_file_full(AT_FDCWD, j, 0, NULL, &data, &size) == -ENXIO);
+ assert_se(read_full_file_full(AT_FDCWD, j, READ_FULL_FILE_CONNECT_SOCKET, clientname, &data, &size) >= 0);
assert_se(size == strlen(TEST_STR));
assert_se(streq(data, TEST_STR));
if (r < 0)
return log_error_errno(r, "Failed to parse root hash signature '%s': %m", argv[6]);
} else {
- r = read_full_file_full(AT_FDCWD, argv[6], READ_FULL_FILE_CONNECT_SOCKET, &hash_sig, &hash_sig_size);
+ r = read_full_file_full(AT_FDCWD, argv[6], READ_FULL_FILE_CONNECT_SOCKET, NULL, &hash_sig, &hash_sig_size);
if (r < 0)
return log_error_errno(r, "Failed to read root hash signature: %m");
}