confd: add support for $factory$ password hash

This patch simplifies the handling of factory default password for the
admin user by overloading the ietf-system password type.  The new type,
$factory$, acts a system hint to use any device specific (or built-in
software/device-tree) default password hash.

Fixes #435

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2024-06-25 17:22:36 +02:00
parent 83d2e1b0ae
commit 174bf7dc6f
8 changed files with 125 additions and 67 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
pkglibexec_SCRIPTS = bootstrap error load gen-service gen-hostkeys gen-admin-auth \
pkglibexec_SCRIPTS = bootstrap error load gen-service gen-hostkeys \
gen-hostname gen-interfaces gen-motd gen-hardware
sbin_SCRIPTS = dagger
-11
View File
@@ -102,12 +102,6 @@ factory()
# shellcheck disable=SC2086
gen-interfaces $GEN_IFACE_OPTS >"$FACTORY_D/20-interfaces.json"
# Extract password for admin user from VPD
if ! gen-admin-auth infix-shell-type:bash >"$FACTORY_D/20-authentication.json"; then
console_error "Unable to create factory-config, gen-admin-auth failed"
return
fi
[ -s "$FACTORY_D/20-hostkey.json" ] || gen-hostkeys >"$FACTORY_D/20-hostkey.json"
# Optional commands (from an overlay) to run for br2-externals
@@ -127,11 +121,6 @@ failure()
gen-hostname "$FAIL_HOSTNAME" >"$FAILURE_D/20-hostname.json"
gen-interfaces >"$FAILURE_D/20-interfaces.json"
# Same password as factory-config, but another login shell
if ! gen-admin-auth infix-shell-type:bash >"$FAILURE_D/20-authentication.json"; then
console_error "Invalid password hash in vital product data, failure-config incomplete!"
fi
[ -s "$FAILURE_D/20-hostkey.json" ] || gen-hostkeys >"$FAILURE_D/20-hostkey.json"
# Optional failure/error config to generate (or override) for br2-externals
-33
View File
@@ -1,33 +0,0 @@
#!/bin/sh
# This script extracts the admin user's password hash from VPD
shell="$1"
pwhash=$(jq -r '."factory-password-hash"' /run/system.json)
if [ -z "$pwhash" ] || [ "$pwhash" = "null" ]; then
# Do not fail, lock account instead. This way developers can enable
# root account login at build-time to diagnose the system.
password=""
rc=1
else
password="\"password\": \"$pwhash\","
rc=0
fi
cat <<EOF
{
"ietf-system:system": {
"authentication": {
"user": [
{
"name": "admin",
${password}
"infix-system:shell": "$shell"
}
]
}
}
}
EOF
exit $rc
+10 -1
View File
@@ -1,5 +1,14 @@
{
"ietf-system:system": {
"hostname": "infix"
"hostname": "infix",
"authentication": {
"user": [
{
"name": "admin",
"password": "$factory$",
"infix-system:shell": "infix-shell-type:bash"
}
]
}
}
}
@@ -0,0 +1,8 @@
{
"ieee802-dot1ab-lldp:lldp": {
"infix-lldp:enabled": true
},
"infix-services:mdns": {
"enabled": true
}
}
+10 -7
View File
@@ -1,11 +1,14 @@
{
"ietf-system:system": {
"hostname": "failure"
},
"ieee802-dot1ab-lldp:lldp": {
"infix-lldp:enabled": true
},
"infix-services:mdns": {
"enabled": true
"hostname": "failure",
"authentication": {
"user": [
{
"name": "admin",
"password": "$factory$",
"infix-system:shell": "infix-shell-type:bash"
}
]
}
}
}
+32 -14
View File
@@ -6,6 +6,7 @@
#include <pwd.h>
#include <grp.h>
#include <shadow.h>
#include <jansson.h>
#include <sys/utsname.h>
#include <sys/sysinfo.h>
@@ -126,8 +127,8 @@ static char *fmtime(time_t t, char *buf, size_t len)
return buf;
}
static sr_error_t _sr_change_iter(sr_session_ctx_t *session, char *xpath,
sr_error_t cb(sr_session_ctx_t *, struct sr_change *))
static sr_error_t _sr_change_iter(sr_session_ctx_t *session, struct confd *confd, char *xpath,
sr_error_t cb(sr_session_ctx_t *, struct confd *, struct sr_change *))
{
struct sr_change change = {};
sr_change_iter_t *iter;
@@ -138,7 +139,7 @@ static sr_error_t _sr_change_iter(sr_session_ctx_t *session, char *xpath,
return err;
while (sr_get_change_next(session, iter, &change.op, &change.old, &change.new) == SR_ERR_OK) {
err = cb(session, &change);
err = cb(session, confd, &change);
sr_free_val(change.old);
sr_free_val(change.new);
if (err) {
@@ -903,12 +904,29 @@ fail:
return -1;
}
static int set_password(const char *user, const char *hash, bool lock)
static int set_password(struct confd *confd, const char *user, const char *hash, bool lock)
{
const char *factory = "$factory$";
struct spwd *sp;
FILE *fp = NULL;
int fd = -1;
if (!strncmp(hash, factory, strlen(factory))) {
struct json_t *pwd;
pwd = json_object_get(confd->root, "factory-password-hash");
if (!pwd || !json_is_string(pwd)) {
/*
* Do not fail, lock account instead. This way developers can
* enable root account login at build-time to diagnose the system.
*/
ERROR("%s: cannot find factory-default password hash!", user);
lock = true;
} else {
hash = json_string_value(pwd);
}
}
if (lckpwdf())
goto exit;
@@ -956,7 +974,7 @@ exit:
return -1;
}
static sr_error_t handle_sr_passwd_update(sr_session_ctx_t *, struct sr_change *change)
static sr_error_t handle_sr_passwd_update(sr_session_ctx_t *, struct confd *confd, struct sr_change *change)
{
sr_error_t err = SR_ERR_OK;
const char *hash;
@@ -988,13 +1006,13 @@ static sr_error_t handle_sr_passwd_update(sr_session_ctx_t *, struct sr_change *
ERROR("Empty passwords are not allowed, disabling password login.");
hash = "*";
}
if (set_password(user, hash, false))
if (set_password(confd, user, hash, false))
err = SR_ERR_SYS;
else
NOTE("Password updated for user %s", user);
break;
case SR_OP_DELETED:
if (set_password(user, "*", true))
if (set_password(confd, user, "*", true))
err = SR_ERR_SYS;
else
NOTE("Password deleted for user %s", user);
@@ -1007,7 +1025,7 @@ static sr_error_t handle_sr_passwd_update(sr_session_ctx_t *, struct sr_change *
return err;
}
static sr_error_t handle_sr_shell_update(sr_session_ctx_t *sess, struct sr_change *change)
static sr_error_t handle_sr_shell_update(sr_session_ctx_t *sess, struct confd *confd, struct sr_change *change)
{
char *shell = NULL;
char *user;
@@ -1034,7 +1052,7 @@ static sr_error_t handle_sr_shell_update(sr_session_ctx_t *sess, struct sr_chang
return err;
}
static sr_error_t check_sr_user_update(sr_session_ctx_t *, struct sr_change *change)
static sr_error_t check_sr_user_update(sr_session_ctx_t *, struct confd *, struct sr_change *change)
{
sr_xpath_ctx_t state;
sr_val_t *val;
@@ -1053,7 +1071,7 @@ static sr_error_t check_sr_user_update(sr_session_ctx_t *, struct sr_change *cha
return SR_ERR_OK;
}
static sr_error_t handle_sr_user_update(sr_session_ctx_t *sess, struct sr_change *change)
static sr_error_t handle_sr_user_update(sr_session_ctx_t *sess, struct confd *, struct sr_change *change)
{
sr_xpath_ctx_t state;
char *name;
@@ -1152,7 +1170,7 @@ static sr_error_t change_auth_check(struct confd *confd, sr_session_ctx_t *sessi
{
sr_error_t err;
err = _sr_change_iter(session, XPATH_AUTH_"/user", check_sr_user_update);
err = _sr_change_iter(session, confd, XPATH_AUTH_"/user", check_sr_user_update);
if (err)
return err;
@@ -1163,15 +1181,15 @@ static sr_error_t change_auth_done(struct confd *confd, sr_session_ctx_t *sessio
{
sr_error_t err;
err = _sr_change_iter(session, XPATH_AUTH_"/user", handle_sr_user_update);
err = _sr_change_iter(session, confd, XPATH_AUTH_"/user", handle_sr_user_update);
if (err)
return err;
err = _sr_change_iter(session, XPATH_AUTH_"/user[*]/password", handle_sr_passwd_update);
err = _sr_change_iter(session, confd, XPATH_AUTH_"/user[*]/password", handle_sr_passwd_update);
if (err)
goto cleanup;
err = _sr_change_iter(session, XPATH_AUTH_"/user[*]/shell", handle_sr_shell_update);
err = _sr_change_iter(session, confd, XPATH_AUTH_"/user[*]/shell", handle_sr_shell_update);
if (err)
goto cleanup;
@@ -79,6 +79,63 @@ module infix-system {
* Typedefs
*/
typedef crypt-hash {
type string {
pattern
'$0$.*'
+ '|$1$[a-zA-Z0-9./]{1,8}$[a-zA-Z0-9./]{22}'
+ '|$5$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{43}'
+ '|$6$(rounds=\d+$)?[a-zA-Z0-9./]{1,16}$[a-zA-Z0-9./]{86}'
+ '|$factory$.*';
}
description
"The crypt-hash type is used to store passwords using a hash
function. This type extends the existing crypt-hash type to
support the reserved string $factory$, which is used for
device-specific factory default hash. It is up to the
underlying system to define this further, but one example
is to use Vital Product Data (VPD) information, e.g., an
onboard EEPROM where a device hash is stored for the initial
'admin' user.
A value of this type matches one of the forms:
$0$<clear text password>
$<id>$<salt>$<password hash>
$<id>$<parameter>$<salt>$<password hash>
The '$0$' prefix signals that the value is clear text. When
such a value is received by the server, a hash value is
calculated, and the string '$<id>$<salt>$' or
$<id>$<parameter>$<salt>$ is prepended to the result. This
value is stored in the configuration data store.
If a value starting with '$<id>$', where <id> is not '0', is
received, the server knows that the value already represents a
hashed value and stores it 'as is' in the data store.
When a server needs to verify a password given by a user, it
finds the stored password hash string for that user, extracts
the salt, and calculates the hash with the salt and given
password as input. If the calculated hash value is the same
as the stored value, the password given by the client is
accepted.
This type defines the following hash functions:
id | hash function | feature
---+---------------+-------------------
1 | MD5 | crypt-hash-md5
5 | SHA-256 | crypt-hash-sha-256
6 | SHA-512 | crypt-hash-sha-512
The server indicates support for the different hash functions
by advertising the corresponding feature.";
reference
"IEEE Std 1003.1-2008 - crypt() function
RFC 1321: The MD5 Message-Digest Algorithm
FIPS.180-4.2012: Secure Hash Standard (SHS)";
}
typedef username {
type string {
pattern "[_a-zA-Z0-9][-._a-zA-Z0-9]*$?";
@@ -186,4 +243,11 @@ module infix-system {
type infix-sys:username;
}
}
deviation "/sys:system/sys:authentication/sys:user/sys:password" {
description "Extended password hash, including missing types, and $factory$.";
deviate replace {
type infix-sys:crypt-hash;
}
}
}