Skip to content

Commit

Permalink
confd: refactor netconf hostkey generation
Browse files Browse the repository at this point in the history
This patch adds support for an empty "genkey" pair in the keystore for
the NETCONF hostkeys.  Primarily intended for static factory-config.

When a configuration is loaded and confd detects a missing public or
private key in the "genkey" asymmetric key, it loads generated keys
from disk and store in the running datastore.

Fixes #435

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
  • Loading branch information
troglobit committed Jun 18, 2024
1 parent 927925c commit aae5466
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 38 deletions.
14 changes: 14 additions & 0 deletions board/common/rootfs/libexec/infix/mkkeys
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
# Generate NETCONF SSH host key pair
set -e

BIT=2048
KEY=$1
PUB=$2

mkdir -p "$(dirname "$KEY")" "$(dirname "$PUB")"

openssl genpkey -quiet -algorithm RSA -pkeyopt rsa_keygen_bits:$BIT -outform PEM > "$KEY"
openssl rsa -RSAPublicKey_out < "$KEY" > "$PUB"

exit 0
4 changes: 2 additions & 2 deletions src/confd/bin/Makefile.am
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pkglibexec_SCRIPTS = bootstrap error load gen-service gen-hostkeys \
gen-hostname gen-interfaces gen-motd gen-hardware
pkglibexec_SCRIPTS = bootstrap error load gen-service gen-hostname \
gen-interfaces gen-motd gen-hardware
sbin_SCRIPTS = dagger
4 changes: 0 additions & 4 deletions src/confd/bin/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,6 @@ factory()
# shellcheck disable=SC2086
gen-interfaces $GEN_IFACE_OPTS >"$FACTORY_D/20-interfaces.json"

[ -s "$FACTORY_D/20-hostkey.json" ] || gen-hostkeys >"$FACTORY_D/20-hostkey.json"

# Optional commands (from an overlay) to run for br2-externals
[ -x "$(command -v gen-ifs-custom)" ] && gen-ifs-custom >"$FACTORY_D/20-interfaces.json"
[ -x "$(command -v gen-cfg-custom)" ] && gen-cfg-custom >"$FACTORY_D/30-config.json"
Expand All @@ -121,8 +119,6 @@ failure()
gen-hostname "$FAIL_HOSTNAME" >"$FAILURE_D/20-hostname.json"
gen-interfaces >"$FAILURE_D/20-interfaces.json"

[ -s "$FAILURE_D/20-hostkey.json" ] || gen-hostkeys >"$FAILURE_D/20-hostkey.json"

# Optional failure/error config to generate (or override) for br2-externals
[ -x "$(command -v gen-err-custom)" ] && gen-err-custom >"$FAILURE_D/30-error.json"

Expand Down
32 changes: 0 additions & 32 deletions src/confd/bin/gen-hostkeys

This file was deleted.

14 changes: 14 additions & 0 deletions src/confd/share/factory.d/10-netconf-server.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
{
"ietf-keystore:keystore": {
"asymmetric-keys": {
"asymmetric-key": [
{
"name": "genkey",
"public-key-format": "ietf-crypto-types:ssh-public-key-format",
"public-key": "",
"private-key-format": "ietf-crypto-types:rsa-private-key-format",
"cleartext-private-key": "",
"certificates": {}
}
]
}
},
"ietf-netconf-server:netconf-server": {
"listen": {
"endpoints": {
Expand Down
1 change: 1 addition & 0 deletions src/confd/src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ confd_plugin_la_SOURCES = \
core.c core.h \
dagger.c dagger.h \
ietf-interfaces.c \
ietf-keystore.c \
ietf-system.c \
ietf-factory-default.c \
ietf-routing.c \
Expand Down
3 changes: 3 additions & 0 deletions src/confd/src/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
if (!confd.root)
goto err;
rc = ietf_interfaces_init(&confd);
if (rc)
goto err;
rc = ietf_keystore_init(&confd);
if (rc)
goto err;
rc = ietf_system_init(&confd);
Expand Down
3 changes: 3 additions & 0 deletions src/confd/src/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,7 @@ int infix_services_init(struct confd *confd);
/* ietf-hardware.c */
int ietf_hardware_init(struct confd *confd);

/* ietf-keystore.c */
int ietf_keystore_init(struct confd *confd);

#endif /* CONFD_CORE_H_ */
135 changes: 135 additions & 0 deletions src/confd/src/ietf-keystore.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/* SPDX-License-Identifier: BSD-3-Clause */

#include <sys/stat.h>
#include <srx/common.h>
#include <srx/lyx.h>

#include <srx/srx_val.h>

#include "base64.h"
#include "core.h"

#define XPATH_KEYSTORE_ "/ietf-keystore:keystore/asymmetric-keys"

static size_t filesz(const char *fn)
{
struct stat st;

if (stat(fn, &st))
st.st_size = BUFSIZ;
return st.st_size;
}

/* sanity check, must exist and be of non-zero size */
static bool fileok(const char *fn)
{
if (!fexist(fn))
return false;

if (filesz(fn) < 42)
return false;

return true;
}

static char *filerd(const char *fn, size_t len)
{
char *buf, *ptr;
FILE *pp;

ptr = buf = malloc(len + 1);
if (!buf)
return NULL;

/* strip ---BEGIN//---END markers and concatenate lines */
pp = popenf("r", "grep -v -- '-----' %s | tr -d '\n'", fn);
if (!pp) {
free(buf);
return NULL;
}

while (fgets(ptr, len, pp)) {
size_t inc = strlen(chomp(ptr));

ptr += inc;
len -= inc;
}
pclose(pp);

return buf;
}

static int change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *module_name,
const char *xpath, sr_event_t event, uint32_t request_id, void *_)
{
const char *priv_keyfile = "/cfg/ssl/private/netconf.key";
const char *pub_keyfile = "/cfg/ssl/public/netconf.pub";
char *pub_key = NULL, *priv_key = NULL;
int rc = SR_ERR_INTERNAL;

switch (event) {
case SR_EV_UPDATE:
/* Check NETCONF default hostkey pair */
break;
default:
return SR_ERR_OK;
}

if (srx_isset(session, XPATH_KEYSTORE_"/asymmetric-key[name='genkey']/cleartext-private-key") &&
srx_isset(session, XPATH_KEYSTORE_"/asymmetric-key[name='genkey']/public-key")) {
return SR_ERR_OK; /* already set */
}
WARN("NETCONF private and public host keys missing in confiugration.");

if (!fileok(priv_keyfile) || !fileok(pub_keyfile)) {
NOTE("Generating NETCONF SSH host keys ...");
if (systemf("/libexec/infix/mkkeys %s %s", priv_keyfile, pub_keyfile))
goto err;
} else {
NOTE("Using existing SSH host keys for NETCONF.");
}

priv_key = filerd(priv_keyfile, filesz(priv_keyfile));
if (!priv_key)
goto err;

pub_key = filerd(pub_keyfile, filesz(pub_keyfile));
if (!pub_key)
goto err;

xpath = XPATH_KEYSTORE_"/asymmetric-key[name='genkey']/cleartext-private-key";
rc = sr_set_item_str(session, xpath, priv_key, NULL, SR_EDIT_NON_RECURSIVE);
if (rc) {
ERROR("Failed setting private key ... rc: %d", rc);
goto err;
}

xpath = XPATH_KEYSTORE_"/asymmetric-key[name='genkey']/public-key";
rc = sr_set_item_str(session, xpath, pub_key, NULL, SR_EDIT_NON_RECURSIVE);
if (rc != SR_ERR_OK) {
ERROR("Failed setting public key ... rc: %d", rc);
goto err;
}

err:
if (pub_key)
free(pub_key);
if (priv_key)
free(priv_key);

if (rc != SR_ERR_OK)
return rc;

return SR_ERR_OK;
}

int ietf_keystore_init(struct confd *confd)
{
int rc;

rc = sr_module_change_subscribe(confd->session, "ietf-keystore", "/ietf-keystore:keystore//.",
change_cb, confd, 0, SR_SUBSCR_UPDATE, &confd->sub);
if (rc)
ERROR("%s failed: %s", __func__, sr_strerror(rc));
return rc;
}

0 comments on commit aae5466

Please sign in to comment.