mirror of
https://github.com/systemd/systemd
synced 2024-09-06 16:56:43 +00:00
user-record: Add blobDirectory and blobManifest
These fields are used to connect a JSON user record to its blob directory, and to include the directory's contents in the record's signature
This commit is contained in:
parent
4006b98da6
commit
1b466c0940
|
@ -234,6 +234,16 @@ optional, when unset the user should not be considered part of any realm. A
|
|||
user record with a realm set is never compatible (for the purpose of updates,
|
||||
see above) with a user record without one set, even if the `userName` field matches.
|
||||
|
||||
`blobDirectory` → The absolute path to a world-readable copy of the user's blob
|
||||
directory. See [Blob Directories](USER_RECORD_BLOB_DIRS.md) for more details.
|
||||
|
||||
`blobManifest` → An object, which maps valid blob directory filenames (see
|
||||
[Blob Directories](USER_RECORD_BLOB_DIRS.md) for requirements) to SHA256 hashes
|
||||
formatted as hex strings. This exists for the purpose of including the contents
|
||||
of the blob directory in the record's signature. Managers that support blob
|
||||
directories and utilize signed user records (like `systemd-homed`) should use
|
||||
this field to verify the contents of the blob directory whenever appropriate.
|
||||
|
||||
`realName` → The real name of the user, a string. This should contain the
|
||||
user's real ("human") name, and corresponds loosely to the GECOS field of
|
||||
classic UNIX user records. When converting a `struct passwd` to a JSON user
|
||||
|
@ -758,7 +768,7 @@ These two are the only two fields specific to this section. All other fields
|
|||
that may be used in this section are identical to the equally named ones in the
|
||||
`regular` section (i.e. at the top-level object). Specifically, these are:
|
||||
|
||||
`iconName`, `location`, `shell`, `umask`, `environment`, `timeZone`,
|
||||
`blobDirectory`, `blobManifest`, `iconName`, `location`, `shell`, `umask`, `environment`, `timeZone`,
|
||||
`preferredLanguage`, `additionalLanguages`, `niceLevel`, `resourceLimits`, `locked`, `notBeforeUSec`,
|
||||
`notAfterUSec`, `storage`, `diskSize`, `diskSizeRelative`, `skeletonDirectory`,
|
||||
`accessMode`, `tasksMax`, `memoryHigh`, `memoryMax`, `cpuWeight`, `ioWeight`,
|
||||
|
@ -810,9 +820,9 @@ The following fields are defined in the `binding` section. They all have an
|
|||
identical format and override their equally named counterparts in the `regular`
|
||||
and `perMachine` sections:
|
||||
|
||||
`imagePath`, `homeDirectory`, `partitionUuid`, `luksUuid`, `fileSystemUuid`,
|
||||
`uid`, `gid`, `storage`, `fileSystemType`, `luksCipher`, `luksCipherMode`,
|
||||
`luksVolumeKeySize`.
|
||||
`blobDirectory`, `imagePath`, `homeDirectory`, `partitionUuid`, `luksUuid`,
|
||||
`fileSystemUuid`, `uid`, `gid`, `storage`, `fileSystemType`, `luksCipher`,
|
||||
`luksCipherMode`, `luksVolumeKeySize`.
|
||||
|
||||
## Fields in the `status` section
|
||||
|
||||
|
@ -1102,6 +1112,7 @@ A fully featured user record associated with a home directory managed by
|
|||
"fileSystemUuid" : "758e88c8-5851-4a2a-b88f-e7474279c111",
|
||||
"gid" : 60232,
|
||||
"homeDirectory" : "/home/grobie",
|
||||
"blobDirectory" : "/var/cache/systemd/homed/grobie/",
|
||||
"imagePath" : "/home/grobie.home",
|
||||
"luksCipher" : "aes",
|
||||
"luksCipherMode" : "xts-plain64",
|
||||
|
@ -1112,6 +1123,10 @@ A fully featured user record associated with a home directory managed by
|
|||
"uid" : 60232
|
||||
}
|
||||
},
|
||||
"blobManifest" : {
|
||||
"avatar" : "c0636851d25a62d817ff7da4e081d1e646e42c74d0ecb53425f75fcf1ba43b52",
|
||||
"login-background" : "da7ad0222a6edbc6cd095149c72d38d92fd3114f606e4b57469857ef47fade18"
|
||||
},
|
||||
"disposition" : "regular",
|
||||
"enforcePasswordPolicy" : false,
|
||||
"lastChangeUSec" : 1565950024279735,
|
||||
|
|
|
@ -15,7 +15,8 @@ system.
|
|||
|
||||
The JSON User Record specifies the location of the blob directory via the
|
||||
`blobDirectory` field. If the field is unset, then there is no blob directory
|
||||
and thus no blob files to look for. The blob directory is completely
|
||||
and thus no blob files to look for. Note that `blobDirectory` can exist in the
|
||||
`regular`, `perMachine`, and `status` sections. The blob directory is completely
|
||||
owned and managed by the service that owns the rest of the user record (as
|
||||
specified in the `service` field).
|
||||
|
||||
|
|
|
@ -3,8 +3,14 @@
|
|||
#include "cap-list.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "hashmap.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "path-util.h"
|
||||
#include "pretty-print.h"
|
||||
#include "process-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "sha256.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "user-record-show.h"
|
||||
|
@ -213,6 +219,37 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) {
|
|||
printf("\n");
|
||||
}
|
||||
|
||||
if (hr->blob_directory) {
|
||||
_cleanup_free_ char **filenames = NULL;
|
||||
size_t n_filenames = 0;
|
||||
|
||||
r = hashmap_dump_keys_sorted(hr->blob_manifest, (void***) &filenames, &n_filenames);
|
||||
if (r < 0) {
|
||||
errno = -r;
|
||||
printf(" Blob Dir.: %s (can't iterate: %m)\n", hr->blob_directory);
|
||||
} else
|
||||
printf(" Blob Dir.: %s\n", hr->blob_directory);
|
||||
|
||||
for (size_t i = 0; i < n_filenames; i++) {
|
||||
_cleanup_free_ char *path = NULL, *link = NULL, *hash = NULL;
|
||||
const char *filename = filenames[i];
|
||||
const uint8_t *hash_bytes = hashmap_get(hr->blob_manifest, filename);
|
||||
bool last = i == n_filenames - 1;
|
||||
|
||||
path = path_join(hr->blob_directory, filename);
|
||||
if (path)
|
||||
(void) terminal_urlify_path(path, filename, &link);
|
||||
hash = hexmem(hash_bytes, SHA256_DIGEST_SIZE);
|
||||
|
||||
printf(" %s %s %s(%s)%s\n",
|
||||
special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH),
|
||||
link ?: filename,
|
||||
ansi_grey(),
|
||||
hash ?: "can't display hash",
|
||||
ansi_normal());
|
||||
}
|
||||
}
|
||||
|
||||
storage = user_record_storage(hr);
|
||||
if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */
|
||||
printf(" Storage: %s%s\n", user_storage_to_string(storage),
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
#include "path-util.h"
|
||||
#include "pkcs11-util.h"
|
||||
#include "rlimit-util.h"
|
||||
#include "sha256.h"
|
||||
#include "string-table.h"
|
||||
#include "strv.h"
|
||||
#include "uid-classification.h"
|
||||
#include "user-record.h"
|
||||
#include "user-util.h"
|
||||
#include "utf8.h"
|
||||
|
||||
#define DEFAULT_RATELIMIT_BURST 30
|
||||
#define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE)
|
||||
|
@ -142,6 +144,9 @@ static UserRecord* user_record_free(UserRecord *h) {
|
|||
free(h->location);
|
||||
free(h->icon_name);
|
||||
|
||||
free(h->blob_directory);
|
||||
hashmap_free(h->blob_manifest);
|
||||
|
||||
free(h->shell);
|
||||
|
||||
strv_free(h->environment);
|
||||
|
@ -1074,6 +1079,7 @@ static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispa
|
|||
static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
|
||||
static const JsonDispatch binding_dispatch_table[] = {
|
||||
{ "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
|
||||
{ "imagePath", JSON_VARIANT_STRING, json_dispatch_image_path, offsetof(UserRecord, image_path), 0 },
|
||||
{ "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 },
|
||||
{ "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 },
|
||||
|
@ -1110,6 +1116,52 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch
|
|||
return json_dispatch(m, binding_dispatch_table, flags, userdata);
|
||||
}
|
||||
|
||||
static int dispatch_blob_manifest(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
|
||||
_cleanup_hashmap_free_ Hashmap *manifest = NULL;
|
||||
Hashmap **ret = ASSERT_PTR(userdata);
|
||||
JsonVariant *value;
|
||||
const char *key;
|
||||
int r;
|
||||
|
||||
if (!variant)
|
||||
return 0;
|
||||
|
||||
if (!json_variant_is_object(variant))
|
||||
return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name));
|
||||
|
||||
JSON_VARIANT_OBJECT_FOREACH(key, value, variant) {
|
||||
_cleanup_free_ char *filename = NULL;
|
||||
_cleanup_free_ uint8_t *hash = NULL;
|
||||
|
||||
if (!json_variant_is_string(value))
|
||||
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key);
|
||||
|
||||
if (!suitable_blob_filename(key))
|
||||
return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key);
|
||||
|
||||
filename = strdup(key);
|
||||
if (!filename)
|
||||
return json_log_oom(value, flags);
|
||||
|
||||
hash = malloc(SHA256_DIGEST_SIZE);
|
||||
if (!hash)
|
||||
return json_log_oom(value, flags);
|
||||
|
||||
r = parse_sha256(json_variant_string(value), hash);
|
||||
if (r < 0)
|
||||
return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, json_variant_string(value));
|
||||
|
||||
r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash);
|
||||
if (r < 0)
|
||||
return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename);
|
||||
TAKE_PTR(filename); /* Ownership transfers to hashmap */
|
||||
TAKE_PTR(hash);
|
||||
}
|
||||
|
||||
hashmap_free_and_replace(*ret, manifest);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags) {
|
||||
sd_id128_t mid;
|
||||
int r;
|
||||
|
@ -1226,6 +1278,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp
|
|||
static const JsonDispatch per_machine_dispatch_table[] = {
|
||||
{ "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 },
|
||||
{ "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
|
||||
{ "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
|
||||
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
|
||||
{ "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 },
|
||||
{ "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 },
|
||||
|
@ -1560,6 +1614,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla
|
|||
static const JsonDispatch user_dispatch_table[] = {
|
||||
{ "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX},
|
||||
{ "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 },
|
||||
{ "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 },
|
||||
{ "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 },
|
||||
{ "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 },
|
||||
{ "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE },
|
||||
{ "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE },
|
||||
|
@ -2373,6 +2429,13 @@ int user_record_test_password_change_required(UserRecord *h) {
|
|||
return change_permitted ? 0 : -EROFS;
|
||||
}
|
||||
|
||||
int suitable_blob_filename(const char *name) {
|
||||
/* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */
|
||||
return filename_is_valid(name) &&
|
||||
in_charset(name, URI_UNRESERVED) &&
|
||||
name[0] != '.';
|
||||
}
|
||||
|
||||
static const char* const user_storage_table[_USER_STORAGE_MAX] = {
|
||||
[USER_CLASSIC] = "classic",
|
||||
[USER_LUKS] = "luks",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "sd-id128.h"
|
||||
|
||||
#include "hashmap.h"
|
||||
#include "json.h"
|
||||
#include "missing_resource.h"
|
||||
#include "time-util.h"
|
||||
|
@ -243,6 +244,9 @@ typedef struct UserRecord {
|
|||
char *icon_name;
|
||||
char *location;
|
||||
|
||||
char *blob_directory;
|
||||
Hashmap *blob_manifest;
|
||||
|
||||
UserDisposition disposition;
|
||||
uint64_t last_change_usec;
|
||||
uint64_t last_password_change_usec;
|
||||
|
@ -449,6 +453,9 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags);
|
|||
int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags);
|
||||
int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask);
|
||||
|
||||
#define BLOB_DIR_MAX_SIZE (UINT64_C(64) * U64_MB)
|
||||
int suitable_blob_filename(const char *name);
|
||||
|
||||
const char* user_storage_to_string(UserStorage t) _const_;
|
||||
UserStorage user_storage_from_string(const char *s) _pure_;
|
||||
|
||||
|
|
Loading…
Reference in a new issue