homectl: write recovery key file via linkable tmpfile
Create the file with open_tmpfile_linkable_at(). Write the recovery key and trailing newline to the temporary fd. Set the final mode to 0600 before publishing it. Link it into place with link_tmpfile_at(..., LINK_TMPFILE_SYNC).
This commit is contained in:
committed by
Yu Watanabe
parent
3f72568682
commit
471e34d5cb
@@ -692,6 +692,17 @@
|
|||||||
|
|
||||||
<xi:include href="version-info.xml" xpointer="v247"/></listitem>
|
<xi:include href="version-info.xml" xpointer="v247"/></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--recovery-key-file=<replaceable>PATH</replaceable></option></term>
|
||||||
|
|
||||||
|
<listitem><para>Attempts to write the generated recovery key to the specified file. The file is
|
||||||
|
created with access mode <literal>0600</literal> and must not exist yet. This option may only be used
|
||||||
|
together with <option>--recovery-key=yes</option>. If the file cannot be written, a warning is shown
|
||||||
|
and the account operation still succeeds. The recovery key is always shown on screen.</para>
|
||||||
|
|
||||||
|
<xi:include href="version-info.xml" xpointer="v262"/></listitem>
|
||||||
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ _homectl() {
|
|||||||
--language
|
--language
|
||||||
--ssh-authorized-keys
|
--ssh-authorized-keys
|
||||||
--pkcs11-token-uri
|
--pkcs11-token-uri
|
||||||
|
--recovery-key
|
||||||
|
--recovery-key-file
|
||||||
--locked
|
--locked
|
||||||
--not-before
|
--not-before
|
||||||
--not-after
|
--not-after
|
||||||
@@ -126,7 +128,7 @@ _homectl() {
|
|||||||
--machine|-M)
|
--machine|-M)
|
||||||
comps=$( __get_machines )
|
comps=$( __get_machines )
|
||||||
;;
|
;;
|
||||||
--identity|--image-path|--avatar|--login-background)
|
--identity|--image-path|--avatar|--login-background|--recovery-key-file)
|
||||||
comps=$(compgen -A file -- "$cur" )
|
comps=$(compgen -A file -- "$cur" )
|
||||||
compopt -o filenames
|
compopt -o filenames
|
||||||
;;
|
;;
|
||||||
@@ -136,7 +138,7 @@ _homectl() {
|
|||||||
--export-format)
|
--export-format)
|
||||||
comps='full stripped minimal'
|
comps='full stripped minimal'
|
||||||
;;
|
;;
|
||||||
--locked|--enforce-password-policy|--password-change-now|--luks-discard|--luks-offline-discard|--nosuid|--nodev|--noexec|--kill-processes|--auto-login)
|
--locked|--recovery-key|--enforce-password-policy|--password-change-now|--luks-discard|--luks-offline-discard|--nosuid|--nodev|--noexec|--kill-processes|--auto-login)
|
||||||
comps='yes no'
|
comps='yes no'
|
||||||
;;
|
;;
|
||||||
-d|--home-dir|--skel|-b|--blob)
|
-d|--home-dir|--skel|-b|--blob)
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "ansi-color.h"
|
#include "ansi-color.h"
|
||||||
|
#include "chase.h"
|
||||||
#include "errno-util.h"
|
#include "errno-util.h"
|
||||||
|
#include "fd-util.h"
|
||||||
#include "glyph-util.h"
|
#include "glyph-util.h"
|
||||||
#include "homectl-recovery-key.h"
|
#include "homectl-recovery-key.h"
|
||||||
|
#include "io-util.h"
|
||||||
#include "json-util.h"
|
#include "json-util.h"
|
||||||
#include "libcrypt-util.h"
|
#include "libcrypt-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "memory-util.h"
|
#include "memory-util.h"
|
||||||
|
#include "path-util.h"
|
||||||
#include "qrcode-util.h"
|
#include "qrcode-util.h"
|
||||||
#include "recovery-key.h"
|
#include "recovery-key.h"
|
||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
|
#include "tmpfile-util.h"
|
||||||
|
|
||||||
static int add_privileged(sd_json_variant **v, const char *hashed) {
|
static int add_privileged(sd_json_variant **v, const char *hashed) {
|
||||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *e = NULL, *w = NULL, *l = NULL;
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *e = NULL, *w = NULL, *l = NULL;
|
||||||
@@ -103,11 +113,12 @@ static int add_secret(sd_json_variant **v, const char *password) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int identity_add_recovery_key(sd_json_variant **v) {
|
int identity_add_recovery_key(sd_json_variant **v, char **ret_recovery_key) {
|
||||||
_cleanup_(erase_and_freep) char *password = NULL, *hashed = NULL;
|
_cleanup_(erase_and_freep) char *password = NULL, *hashed = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(v);
|
assert(v);
|
||||||
|
assert(ret_recovery_key);
|
||||||
|
|
||||||
/* First, let's generate a secret key */
|
/* First, let's generate a secret key */
|
||||||
r = make_recovery_key(&password);
|
r = make_recovery_key(&password);
|
||||||
@@ -134,6 +145,13 @@ int identity_add_recovery_key(sd_json_variant **v) {
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
*ret_recovery_key = TAKE_PTR(password);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void show_recovery_key(const char *recovery_key) {
|
||||||
|
assert(recovery_key);
|
||||||
|
|
||||||
/* We output the key itself with a trailing newline to stdout and the decoration around it to stderr
|
/* We output the key itself with a trailing newline to stdout and the decoration around it to stderr
|
||||||
* instead. */
|
* instead. */
|
||||||
|
|
||||||
@@ -146,7 +164,7 @@ int identity_add_recovery_key(sd_json_variant **v) {
|
|||||||
ansi_highlight());
|
ansi_highlight());
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
|
||||||
fputs(password, stdout);
|
fputs(recovery_key, stdout);
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
fputs(ansi_normal(), stderr);
|
fputs(ansi_normal(), stderr);
|
||||||
@@ -161,7 +179,48 @@ int identity_add_recovery_key(sd_json_variant **v) {
|
|||||||
"whenever authentication is requested.\n", stderr);
|
"whenever authentication is requested.\n", stderr);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
|
||||||
(void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", password);
|
(void) print_qrcode(stderr, "Optionally scan the recovery key for safekeeping", recovery_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int recovery_key_file_write(const char *path, const char *recovery_key) {
|
||||||
|
_cleanup_free_ char *filename = NULL, *tmp = NULL;
|
||||||
|
_cleanup_close_ int dir_fd = -EBADF, fd = -EBADF;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(path);
|
||||||
|
assert(recovery_key);
|
||||||
|
|
||||||
|
r = chase(path,
|
||||||
|
/* root= */ NULL,
|
||||||
|
CHASE_PARENT|CHASE_EXTRACT_FILENAME|CHASE_PROHIBIT_SYMLINKS,
|
||||||
|
&filename,
|
||||||
|
&dir_fd);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (!filename_is_valid(filename))
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
fd = open_tmpfile_linkable_at(dir_fd, filename, O_WRONLY|O_CLOEXEC, &tmp);
|
||||||
|
if (fd < 0)
|
||||||
|
return fd;
|
||||||
|
|
||||||
|
CLEANUP_TMPFILE_AT(dir_fd, tmp);
|
||||||
|
|
||||||
|
if (fchmod(fd, 0600) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
r = loop_write(fd, recovery_key, strlen(recovery_key));
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = loop_write(fd, "\n", 1);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = link_tmpfile_at(fd, dir_fd, tmp, filename, LINK_TMPFILE_SYNC);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
tmp = mfree(tmp); /* disarm CLEANUP_TMPFILE_AT() */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,6 @@
|
|||||||
|
|
||||||
#include "forward.h"
|
#include "forward.h"
|
||||||
|
|
||||||
int identity_add_recovery_key(sd_json_variant **v);
|
int identity_add_recovery_key(sd_json_variant **v, char **ret_recovery_key);
|
||||||
|
void show_recovery_key(const char *recovery_key);
|
||||||
|
int recovery_key_file_write(const char *path, const char *recovery_key);
|
||||||
|
|||||||
+49
-6
@@ -106,6 +106,7 @@ static int arg_fido2_cred_alg = COSE_ES256;
|
|||||||
static int arg_fido2_cred_alg = 0;
|
static int arg_fido2_cred_alg = 0;
|
||||||
#endif
|
#endif
|
||||||
static bool arg_recovery_key = false;
|
static bool arg_recovery_key = false;
|
||||||
|
static char *arg_recovery_key_file = NULL;
|
||||||
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
|
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF;
|
||||||
static ExportFormat arg_export_format = EXPORT_FORMAT_FULL;
|
static ExportFormat arg_export_format = EXPORT_FORMAT_FULL;
|
||||||
static uint64_t arg_capability_bounding_set = CAP_MASK_UNSET;
|
static uint64_t arg_capability_bounding_set = CAP_MASK_UNSET;
|
||||||
@@ -131,6 +132,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_identity_filter, strv_freep);
|
|||||||
STATIC_DESTRUCTOR_REGISTER(arg_identity_filter_rlimits, strv_freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_identity_filter_rlimits, strv_freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, strv_freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, strv_freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, strv_freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, strv_freep);
|
||||||
|
STATIC_DESTRUCTOR_REGISTER(arg_recovery_key_file, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_blob_dir, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_blob_dir, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_blob_files, hashmap_freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_blob_files, hashmap_freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_key_name, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_key_name, freep);
|
||||||
@@ -157,6 +159,7 @@ static bool identity_properties_specified(void) {
|
|||||||
!strv_isempty(arg_identity_filter_rlimits) ||
|
!strv_isempty(arg_identity_filter_rlimits) ||
|
||||||
!strv_isempty(arg_pkcs11_token_uri) ||
|
!strv_isempty(arg_pkcs11_token_uri) ||
|
||||||
!strv_isempty(arg_fido2_device) ||
|
!strv_isempty(arg_fido2_device) ||
|
||||||
|
arg_recovery_key ||
|
||||||
arg_blob_dir ||
|
arg_blob_dir ||
|
||||||
arg_blob_clear ||
|
arg_blob_clear ||
|
||||||
!hashmap_isempty(arg_blob_files);
|
!hashmap_isempty(arg_blob_files);
|
||||||
@@ -1016,12 +1019,14 @@ static int add_disposition(sd_json_variant **v) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int acquire_new_home_record(sd_json_variant *input, UserRecord **ret) {
|
static int acquire_new_home_record(sd_json_variant *input, UserRecord **ret, char **ret_recovery_key) {
|
||||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||||
|
_cleanup_(erase_and_freep) char *recovery_key = NULL;
|
||||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(ret);
|
assert(ret);
|
||||||
|
assert(ret_recovery_key);
|
||||||
|
|
||||||
if (arg_identity) {
|
if (arg_identity) {
|
||||||
unsigned line = 0, column = 0;
|
unsigned line = 0, column = 0;
|
||||||
@@ -1062,7 +1067,7 @@ static int acquire_new_home_record(sd_json_variant *input, UserRecord **ret) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (arg_recovery_key) {
|
if (arg_recovery_key) {
|
||||||
r = identity_add_recovery_key(&v);
|
r = identity_add_recovery_key(&v, &recovery_key);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -1094,6 +1099,7 @@ static int acquire_new_home_record(sd_json_variant *input, UserRecord **ret) {
|
|||||||
return r;
|
return r;
|
||||||
|
|
||||||
*ret = TAKE_PTR(hr);
|
*ret = TAKE_PTR(hr);
|
||||||
|
*ret_recovery_key = TAKE_PTR(recovery_key);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1316,13 +1322,31 @@ static int bus_message_append_blobs(sd_bus_message *m, Hashmap *blobs) {
|
|||||||
return sd_bus_message_close_container(m);
|
return sd_bus_message_close_container(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void finalize_recovery_key(const char *recovery_key, const char *recovery_key_file) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (!recovery_key)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (recovery_key_file) {
|
||||||
|
r = recovery_key_file_write(recovery_key_file, recovery_key);
|
||||||
|
if (r < 0)
|
||||||
|
log_warning_errno(r,
|
||||||
|
"Failed to write recovery key file '%s', ignoring: %m",
|
||||||
|
recovery_key_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
show_recovery_key(recovery_key);
|
||||||
|
}
|
||||||
|
|
||||||
static int create_home_common(sd_json_variant *input, bool show_enforce_password_policy_hint) {
|
static int create_home_common(sd_json_variant *input, bool show_enforce_password_policy_hint) {
|
||||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||||
|
_cleanup_(erase_and_freep) char *recovery_key = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
r = acquire_new_home_record(input, &hr);
|
r = acquire_new_home_record(input, &hr, &recovery_key);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
@@ -1369,6 +1393,7 @@ static int create_home_common(sd_json_variant *input, bool show_enforce_password
|
|||||||
|
|
||||||
if (arg_dry_run) {
|
if (arg_dry_run) {
|
||||||
sd_json_variant_dump(hr->json, SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_NEWLINE, stderr, /* prefix= */ NULL);
|
sd_json_variant_dump(hr->json, SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_NEWLINE, stderr, /* prefix= */ NULL);
|
||||||
|
finalize_recovery_key(recovery_key, /* recovery_key_file= */ NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1430,6 +1455,7 @@ static int create_home_common(sd_json_variant *input, bool show_enforce_password
|
|||||||
break; /* done */
|
break; /* done */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalize_recovery_key(recovery_key, arg_recovery_key_file);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1473,13 +1499,16 @@ static int verb_create_home(int argc, char *argv[], uintptr_t _data, void *userd
|
|||||||
static int acquire_updated_home_record(
|
static int acquire_updated_home_record(
|
||||||
sd_bus *bus,
|
sd_bus *bus,
|
||||||
const char *username,
|
const char *username,
|
||||||
UserRecord **ret) {
|
UserRecord **ret,
|
||||||
|
char **ret_recovery_key) {
|
||||||
|
|
||||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
_cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL;
|
||||||
|
_cleanup_(erase_and_freep) char *recovery_key = NULL;
|
||||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
_cleanup_(user_record_unrefp) UserRecord *hr = NULL;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(ret);
|
assert(ret);
|
||||||
|
assert(ret_recovery_key);
|
||||||
|
|
||||||
if (arg_identity) {
|
if (arg_identity) {
|
||||||
unsigned line = 0, column = 0;
|
unsigned line = 0, column = 0;
|
||||||
@@ -1561,7 +1590,7 @@ static int acquire_updated_home_record(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (arg_recovery_key) {
|
if (arg_recovery_key) {
|
||||||
r = identity_add_recovery_key(&json);
|
r = identity_add_recovery_key(&json, &recovery_key);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
@@ -1584,6 +1613,7 @@ static int acquire_updated_home_record(
|
|||||||
return r;
|
return r;
|
||||||
|
|
||||||
*ret = TAKE_PTR(hr);
|
*ret = TAKE_PTR(hr);
|
||||||
|
*ret_recovery_key = TAKE_PTR(recovery_key);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1617,6 +1647,7 @@ static int verb_update_home(int argc, char *argv[], uintptr_t _data, void *userd
|
|||||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||||
_cleanup_(user_record_unrefp) UserRecord *hr = NULL, *secret = NULL;
|
_cleanup_(user_record_unrefp) UserRecord *hr = NULL, *secret = NULL;
|
||||||
_cleanup_free_ char *buffer = NULL;
|
_cleanup_free_ char *buffer = NULL;
|
||||||
|
_cleanup_(erase_and_freep) char *recovery_key = NULL;
|
||||||
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
_cleanup_hashmap_free_ Hashmap *blobs = NULL;
|
||||||
const char *username;
|
const char *username;
|
||||||
uint64_t flags = 0;
|
uint64_t flags = 0;
|
||||||
@@ -1642,7 +1673,7 @@ static int verb_update_home(int argc, char *argv[], uintptr_t _data, void *userd
|
|||||||
|
|
||||||
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
|
(void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
|
||||||
|
|
||||||
r = acquire_updated_home_record(bus, username, &hr);
|
r = acquire_updated_home_record(bus, username, &hr, &recovery_key);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
@@ -1661,6 +1692,7 @@ static int verb_update_home(int argc, char *argv[], uintptr_t _data, void *userd
|
|||||||
|
|
||||||
if (arg_dry_run) {
|
if (arg_dry_run) {
|
||||||
sd_json_variant_dump(hr->json, SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_NEWLINE, stderr, /* prefix= */ NULL);
|
sd_json_variant_dump(hr->json, SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_NEWLINE, stderr, /* prefix= */ NULL);
|
||||||
|
finalize_recovery_key(recovery_key, /* recovery_key_file= */ NULL);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1716,6 +1748,8 @@ static int verb_update_home(int argc, char *argv[], uintptr_t _data, void *userd
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalize_recovery_key(recovery_key, arg_recovery_key_file);
|
||||||
|
|
||||||
if (and_resize)
|
if (and_resize)
|
||||||
log_info("Resizing home.");
|
log_info("Resizing home.");
|
||||||
|
|
||||||
@@ -4370,6 +4404,12 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
|
|||||||
return r;
|
return r;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
OPTION_LONG("recovery-key-file", "PATH", "Write the generated recovery key to a file"):
|
||||||
|
r = parse_path_argument(opts.arg, /* suppress_root= */ false, &arg_recovery_key_file);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
break;
|
||||||
|
|
||||||
OPTION_GROUP("Blob Directory User Record Properties"): {}
|
OPTION_GROUP("Blob Directory User Record Properties"): {}
|
||||||
|
|
||||||
OPTION('b', "blob", "[FILENAME=]PATH",
|
OPTION('b', "blob", "[FILENAME=]PATH",
|
||||||
@@ -4877,6 +4917,9 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (arg_recovery_key_file && !arg_recovery_key)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--recovery-key-file= requires --recovery-key=yes.");
|
||||||
|
|
||||||
*remaining_args = option_parser_get_args(&opts);
|
*remaining_args = option_parser_get_args(&opts);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,6 +267,76 @@ testcase_basic() {
|
|||||||
homectl remove test-user
|
homectl remove test-user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testcase_recovery_key_file() (
|
||||||
|
local KEY_DIR
|
||||||
|
KEY_DIR="$(mktemp -d /tmp/homed-recovery-key-file.XXXXXX)"
|
||||||
|
local KEY_FILE="$KEY_DIR/recovery-key"
|
||||||
|
local KEY_FILE2="$KEY_DIR/recovery-key-update"
|
||||||
|
local DRY_RUN_OUTPUT="$KEY_DIR/dry-run.out"
|
||||||
|
local DRY_RUN_ERROR="$KEY_DIR/dry-run.err"
|
||||||
|
local UPDATE_EXISTING_OUTPUT="$KEY_DIR/update-existing.out"
|
||||||
|
local KEY_FILE_CONTENT
|
||||||
|
|
||||||
|
homectl remove recoverykeyfiletest 2>/dev/null || true
|
||||||
|
|
||||||
|
trap 'homectl remove recoverykeyfiletest 2>/dev/null || true; rm -rf "$KEY_DIR"' EXIT ERR
|
||||||
|
|
||||||
|
(! NEWPASSWORD=Secr3tRecovery \
|
||||||
|
homectl create recoverykeyfiletest \
|
||||||
|
--storage=directory \
|
||||||
|
--recovery-key-file="$KEY_FILE" \
|
||||||
|
--enforce-password-policy=no )
|
||||||
|
|
||||||
|
SYSTEMD_HOME_DRY_RUN=1 \
|
||||||
|
NEWPASSWORD=Secr3tRecovery \
|
||||||
|
homectl create recoverykeyfiletest \
|
||||||
|
--storage=directory \
|
||||||
|
--recovery-key=yes \
|
||||||
|
--recovery-key-file="$KEY_FILE" \
|
||||||
|
--enforce-password-policy=no \
|
||||||
|
>"$DRY_RUN_OUTPUT" \
|
||||||
|
2>"$DRY_RUN_ERROR"
|
||||||
|
grep -E '^[cbdefghijklnrtuv]{8}(-[cbdefghijklnrtuv]{8}){7}$' "$DRY_RUN_OUTPUT" >/dev/null
|
||||||
|
test ! -e "$KEY_FILE"
|
||||||
|
|
||||||
|
NEWPASSWORD=Secr3tRecovery \
|
||||||
|
homectl create recoverykeyfiletest \
|
||||||
|
--storage=directory \
|
||||||
|
--recovery-key=yes \
|
||||||
|
--recovery-key-file="$KEY_FILE" \
|
||||||
|
--rate-limit-interval=1s \
|
||||||
|
--rate-limit-burst=1000 \
|
||||||
|
--enforce-password-policy=no
|
||||||
|
|
||||||
|
test -s "$KEY_FILE"
|
||||||
|
[[ "$(stat -c "%a" "$KEY_FILE")" == "600" ]]
|
||||||
|
grep -E '^[cbdefghijklnrtuv]{8}(-[cbdefghijklnrtuv]{8}){7}$' "$KEY_FILE" >/dev/null
|
||||||
|
PASSWORD="$(cat "$KEY_FILE")" homectl authenticate recoverykeyfiletest
|
||||||
|
|
||||||
|
KEY_FILE_CONTENT="$(cat "$KEY_FILE")"
|
||||||
|
PASSWORD=Secr3tRecovery \
|
||||||
|
homectl update recoverykeyfiletest \
|
||||||
|
--recovery-key=yes \
|
||||||
|
--recovery-key-file="$KEY_FILE" \
|
||||||
|
>"$UPDATE_EXISTING_OUTPUT"
|
||||||
|
[[ "$(cat "$KEY_FILE")" == "$KEY_FILE_CONTENT" ]]
|
||||||
|
grep -E '^[cbdefghijklnrtuv]{8}(-[cbdefghijklnrtuv]{8}){7}$' "$UPDATE_EXISTING_OUTPUT" >/dev/null
|
||||||
|
PASSWORD="$(cat "$UPDATE_EXISTING_OUTPUT")" homectl authenticate recoverykeyfiletest
|
||||||
|
|
||||||
|
PASSWORD=Secr3tRecovery \
|
||||||
|
homectl update recoverykeyfiletest \
|
||||||
|
--recovery-key=yes \
|
||||||
|
--recovery-key-file="$KEY_FILE2"
|
||||||
|
|
||||||
|
test -s "$KEY_FILE2"
|
||||||
|
[[ "$(stat -c "%a" "$KEY_FILE2")" == "600" ]]
|
||||||
|
grep -E '^[cbdefghijklnrtuv]{8}(-[cbdefghijklnrtuv]{8}){7}$' "$KEY_FILE2" >/dev/null
|
||||||
|
PASSWORD="$(cat "$KEY_FILE2")" homectl authenticate recoverykeyfiletest
|
||||||
|
|
||||||
|
homectl remove recoverykeyfiletest
|
||||||
|
rm -rf "$KEY_DIR"
|
||||||
|
)
|
||||||
|
|
||||||
testcase_blob() {
|
testcase_blob() {
|
||||||
# blob directory tests
|
# blob directory tests
|
||||||
# See docs/USER_RECORD_BLOB_DIRS.md
|
# See docs/USER_RECORD_BLOB_DIRS.md
|
||||||
@@ -1115,6 +1185,7 @@ testcase_identity_groups() {
|
|||||||
|
|
||||||
machinectl shell idgrouptest@ /usr/bin/bash -euxo pipefail -c "jq '.memberOf = ((.memberOf // []) + [\"systemd-journal\"] | unique) | .lastChangeUSec = ((.lastChangeUSec // 0) + 3600000000)' /home/idgrouptest/.identity > /home/idgrouptest/.identity.new && mv -f /home/idgrouptest/.identity.new /home/idgrouptest/.identity"
|
machinectl shell idgrouptest@ /usr/bin/bash -euxo pipefail -c "jq '.memberOf = ((.memberOf // []) + [\"systemd-journal\"] | unique) | .lastChangeUSec = ((.lastChangeUSec // 0) + 3600000000)' /home/idgrouptest/.identity > /home/idgrouptest/.identity.new && mv -f /home/idgrouptest/.identity.new /home/idgrouptest/.identity"
|
||||||
jq -e '.memberOf | index("systemd-journal") != null' /home/idgrouptest/.identity
|
jq -e '.memberOf | index("systemd-journal") != null' /home/idgrouptest/.identity
|
||||||
|
wait_for_state idgrouptest active
|
||||||
|
|
||||||
PASSWORD=foobar homectl authenticate idgrouptest
|
PASSWORD=foobar homectl authenticate idgrouptest
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user