mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-06 07:33:01 +02:00
Add support for configurable SSH server
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
HostKey /var/lib/ssh/ssh_host_rsa_key
|
||||
HostKey /var/lib/ssh/ssh_host_ecdsa_key
|
||||
HostKey /var/lib/ssh/ssh_host_ed25519_key
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Store and convert RSA PUBLIC/PRIVATE KEYs to be able to use them in
|
||||
# OpenSSHd.
|
||||
set -e
|
||||
|
||||
NAME="$1"
|
||||
DIR="$2"
|
||||
PUBLIC="$3"
|
||||
PRIVATE="$4"
|
||||
TMP="$(mktemp)"
|
||||
|
||||
echo -e '-----BEGIN RSA PRIVATE KEY-----' > "$DIR/$NAME"
|
||||
echo "$PRIVATE" >> "$DIR/$NAME"
|
||||
echo -e '-----END RSA PRIVATE KEY-----' >> "$DIR/$NAME"
|
||||
|
||||
echo -e "-----BEGIN RSA PUBLIC KEY-----" > "$TMP"
|
||||
echo -e "$PUBLIC" >> "$TMP"
|
||||
echo -e "-----END RSA PUBLIC KEY-----" >> "$TMP"
|
||||
|
||||
ssh-keygen -i -m PKCS8 -f "$TMP" > "$DIR/$NAME.pub"
|
||||
chmod 0600 "$DIR/$NAME.pub"
|
||||
chmod 0600 "$DIR/$NAME"
|
||||
chown sshd:sshd "$DIR/$NAME.pub"
|
||||
chown sshd:sshd "$DIR/$NAME"
|
||||
@@ -146,15 +146,6 @@ endef
|
||||
SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_MINI_SNMPD
|
||||
endif
|
||||
|
||||
# OpenSSH
|
||||
ifeq ($(BR2_PACKAGE_OPENSSH),y)
|
||||
define SKELETON_INIT_FINIT_SET_OPENSSH
|
||||
cp $(SKELETON_INIT_FINIT_AVAILABLE)/sshd.conf $(FINIT_D)/available/
|
||||
ln -sf ../available/sshd.conf $(FINIT_D)/enabled/sshd.conf
|
||||
endef
|
||||
SKELETON_INIT_FINIT_POST_INSTALL_TARGET_HOOKS += SKELETON_INIT_FINIT_SET_OPENSSH
|
||||
endif
|
||||
|
||||
ifeq ($(BR2_PACKAGE_QUAGGA),y)
|
||||
define SKELETON_INIT_FINIT_SET_QUAGGA
|
||||
cp $(SKELETON_INIT_FINIT_AVAILABLE)/quagga/zebra.conf $(FINIT_D)/available/
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
task <pid/syslogd> \
|
||||
[S] /usr/bin/ssh-hostkeys -- Verifying SSH host keys
|
||||
service <task/ssh-hostkeys/success> env:-/etc/default/sshd \
|
||||
service <pid/syslogd> env:-/etc/default/sshd \
|
||||
[2345] /usr/sbin/sshd -D $SSHD_OPTS -- OpenSSH daemon
|
||||
|
||||
@@ -5,6 +5,22 @@
|
||||
"infix-services:mdns": {
|
||||
"enabled": true
|
||||
},
|
||||
"infix-services:ssh": {
|
||||
"enabled": true,
|
||||
"listen": [
|
||||
{
|
||||
"name": "ipv4",
|
||||
"address": "0.0.0.0",
|
||||
"port": 22
|
||||
},
|
||||
{
|
||||
"name": "ipv6",
|
||||
"address": "::1",
|
||||
"port": 22
|
||||
}
|
||||
],
|
||||
"hostkey": [ "genkey" ]
|
||||
},
|
||||
"infix-services:web": {
|
||||
"enabled": true,
|
||||
"console": {
|
||||
|
||||
@@ -10,5 +10,21 @@
|
||||
"restconf": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"infix-services:ssh": {
|
||||
"enabled": true,
|
||||
"listen": [
|
||||
{
|
||||
"name": "ipv4",
|
||||
"address": "0.0.0.0",
|
||||
"port": 22
|
||||
},
|
||||
{
|
||||
"name": "ipv6",
|
||||
"address": "::1",
|
||||
"port": 22
|
||||
}
|
||||
],
|
||||
"hostkey": [ "genkey" ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,21 @@
|
||||
"restconf": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"infix-services:ssh": {
|
||||
"enabled": true,
|
||||
"listen": [
|
||||
{
|
||||
"name": "ipv4",
|
||||
"address": "0.0.0.0",
|
||||
"port": 22
|
||||
},
|
||||
{
|
||||
"name": "ipv6",
|
||||
"address": "::",
|
||||
"port": 22
|
||||
}
|
||||
],
|
||||
"hostkey": [ "genkey" ]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,19 @@
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <srx/common.h>
|
||||
#include <srx/helpers.h>
|
||||
#include <srx/lyx.h>
|
||||
#include <srx/srx_val.h>
|
||||
|
||||
#include "core.h"
|
||||
#include <sysrepo_types.h>
|
||||
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
||||
#define SSH_HOSTKEYS "/etc/ssh/hostkeys"
|
||||
#define SSH_HOSTKEYS_NEXT SSH_HOSTKEYS"+"
|
||||
|
||||
#define FOREACH_SVC(SVC) \
|
||||
SVC(none) \
|
||||
SVC(ssh) \
|
||||
@@ -27,6 +32,11 @@
|
||||
SVC(netbrowse) \
|
||||
SVC(all) /* must be last entry */
|
||||
|
||||
#define SSH_BASE "/etc/ssh"
|
||||
#define SSHD_CONFIG_BASE SSH_BASE "/sshd_config.d"
|
||||
#define SSHD_CONFIG_LISTEN SSHD_CONFIG_BASE "/listen.conf"
|
||||
#define SSHD_CONFIG_HOSTKEY SSHD_CONFIG_BASE "/host-keys.conf"
|
||||
|
||||
typedef enum {
|
||||
FOREACH_SVC(GENERATE_ENUM)
|
||||
} svc;
|
||||
@@ -135,6 +145,8 @@ static int svc_change(sr_session_ctx_t *session, sr_event_t event, const char *x
|
||||
ena = lydx_is_enabled(srv, "enabled");
|
||||
if (systemf("initctl -nbq %s %s", ena ? "enable" : "disable", svc))
|
||||
ERROR("Failed %s %s", ena ? "enabling" : "disabling", name);
|
||||
if (ena)
|
||||
systemf("initctl -nbq touch %s", svc); /* in case already enabled */
|
||||
|
||||
return put(cfg, srv);
|
||||
}
|
||||
@@ -342,6 +354,74 @@ static int restconf_change(sr_session_ctx_t *session, uint32_t sub_id, const cha
|
||||
return put(cfg, srv);
|
||||
}
|
||||
|
||||
static int ssh_change(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
|
||||
const char *xpath, sr_event_t event, unsigned request_id, void *_confd)
|
||||
{
|
||||
struct lyd_node *ssh = NULL, *listen, *host_key;
|
||||
sr_error_t rc = SR_ERR_OK;
|
||||
sr_data_t *cfg;
|
||||
FILE *fp;
|
||||
|
||||
switch (event) {
|
||||
case SR_EV_DONE:
|
||||
return svc_change(session, event, xpath, "ssh", "sshd");
|
||||
case SR_EV_ENABLED:
|
||||
case SR_EV_CHANGE:
|
||||
break;
|
||||
|
||||
case SR_EV_ABORT:
|
||||
default:
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
if (sr_get_data(session, xpath, 0, 0, 0, &cfg) || !cfg) {
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
ssh = cfg->tree;
|
||||
|
||||
if (!lydx_is_enabled(ssh, "enabled")) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
fp = fopen(SSHD_CONFIG_HOSTKEY, "w");
|
||||
if (!fp) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
LY_LIST_FOR(lydx_get_child(ssh, "hostkey"), host_key) {
|
||||
const char *keyname = lyd_get_value(host_key);
|
||||
if (!keyname)
|
||||
continue;
|
||||
fprintf(fp, "HostKey %s/hostkeys/%s\n", SSH_BASE, keyname);
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
fp = fopen(SSHD_CONFIG_LISTEN, "w");
|
||||
if (!fp) {
|
||||
rc = SR_ERR_INTERNAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
LY_LIST_FOR(lydx_get_child(ssh, "listen"), listen) {
|
||||
const char *address, *port;
|
||||
int ipv6;
|
||||
|
||||
address = lydx_get_cattr(listen, "address");
|
||||
ipv6 = !!strchr(address, ':');
|
||||
port = lydx_get_cattr(listen, "port");
|
||||
|
||||
fprintf(fp, "ListenAddress %s%s%s:%s\n", ipv6 ? "[" : "", address, ipv6 ? "]" : "", port);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
out:
|
||||
sr_release_data(cfg);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int web_change(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
|
||||
const char *xpath, sr_event_t event, unsigned request_id, void *_confd)
|
||||
{
|
||||
@@ -370,6 +450,71 @@ static int web_change(sr_session_ctx_t *session, uint32_t sub_id, const char *mo
|
||||
return put(cfg, srv);
|
||||
}
|
||||
|
||||
/* Store SSH public/private keys */
|
||||
static int change_keystore_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 *_)
|
||||
{
|
||||
int rc = SR_ERR_OK;
|
||||
sr_data_t *cfg;
|
||||
struct lyd_node *changes, *change;
|
||||
|
||||
switch (event) {
|
||||
case SR_EV_CHANGE:
|
||||
case SR_EV_ENABLED:
|
||||
break;
|
||||
case SR_EV_ABORT:
|
||||
/* Remove */
|
||||
if(fexist(SSH_HOSTKEYS_NEXT))
|
||||
rmrf(SSH_HOSTKEYS_NEXT);
|
||||
return SR_ERR_OK;
|
||||
case SR_EV_DONE:
|
||||
if(fexist(SSH_HOSTKEYS_NEXT)) {
|
||||
if(fexist(SSH_HOSTKEYS))
|
||||
if(rmrf(SSH_HOSTKEYS)) {
|
||||
ERROR("Failed to remove old SSH hostkeys: %d", errno);
|
||||
}
|
||||
rename(SSH_HOSTKEYS_NEXT, SSH_HOSTKEYS);
|
||||
svc_change(session, event, "/infix-services:ssh", "ssh", "sshd");
|
||||
}
|
||||
return SR_ERR_OK;
|
||||
|
||||
default:
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
|
||||
if (sr_get_data(session, "/ietf-keystore:keystore/asymmetric-keys//.", 0, 0, 0, &cfg) || !cfg) {
|
||||
return SR_ERR_OK;
|
||||
}
|
||||
changes = lydx_get_descendant(cfg->tree, "keystore", "asymmetric-keys", "asymmetric-key", NULL);
|
||||
|
||||
LYX_LIST_FOR_EACH(changes, change, "asymmetric-key") {
|
||||
const char *name, *private_key_type, *public_key_type;
|
||||
const char *private_key, *public_key;
|
||||
|
||||
name = lydx_get_cattr(change, "name");
|
||||
private_key_type = lydx_get_cattr(change, "private-key-format");
|
||||
public_key_type = lydx_get_cattr(change, "public-key-format");
|
||||
|
||||
if (strcmp(private_key_type, "ietf-crypto-types:rsa-private-key-format")) {
|
||||
INFO("Private key %s is not of SSH type", name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strcmp(public_key_type, "ietf-crypto-types:ssh-public-key-format")) {
|
||||
INFO("Public key %s is not of SSH type", name);
|
||||
continue;
|
||||
}
|
||||
private_key = lydx_get_cattr(change, "cleartext-private-key");
|
||||
public_key = lydx_get_cattr(change, "public-key");
|
||||
mkdir(SSH_HOSTKEYS_NEXT, 0600);
|
||||
if(systemf("/usr/libexec/infix/mksshkey %s %s %s %s", name, SSH_HOSTKEYS_NEXT, public_key, private_key))
|
||||
rc = SR_ERR_INTERNAL;
|
||||
}
|
||||
|
||||
sr_release_data(cfg);
|
||||
|
||||
return rc;
|
||||
}
|
||||
int infix_services_init(struct confd *confd)
|
||||
{
|
||||
int rc;
|
||||
@@ -378,7 +523,8 @@ int infix_services_init(struct confd *confd)
|
||||
0, mdns_change, confd, &confd->sub);
|
||||
REGISTER_MONITOR(confd->session, "ietf-system", "/ietf-system:system/hostname",
|
||||
0, hostname_change, confd, &confd->sub);
|
||||
|
||||
REGISTER_CHANGE(confd->session, "infix-services", "/infix-services:ssh",
|
||||
0, ssh_change, confd, &confd->sub);
|
||||
REGISTER_CHANGE(confd->session, "infix-services", "/infix-services:web",
|
||||
0, web_change, confd, &confd->sub);
|
||||
REGISTER_CHANGE(confd->session, "infix-services", "/infix-services:web/infix-services:console",
|
||||
@@ -390,6 +536,10 @@ int infix_services_init(struct confd *confd)
|
||||
REGISTER_CHANGE(confd->session, "ieee802-dot1ab-lldp", "/ieee802-dot1ab-lldp:lldp",
|
||||
0, lldp_change, confd, &confd->sub);
|
||||
|
||||
/* Store SSH keys */
|
||||
REGISTER_CHANGE(confd->session, "ietf-keystore", "/ietf-keystore:keystore//.",
|
||||
0, change_keystore_cb, confd, &confd->sub);
|
||||
|
||||
return SR_ERR_OK;
|
||||
fail:
|
||||
ERROR("init failed: %s", sr_strerror(rc));
|
||||
|
||||
@@ -33,7 +33,7 @@ MODULES=(
|
||||
"infix-dhcp-client@2024-09-20.yang"
|
||||
"infix-meta@2024-10-18.yang"
|
||||
"infix-system@2024-11-27.yang"
|
||||
"infix-services@2024-12-02.yang"
|
||||
"infix-services@2024-12-03.yang"
|
||||
"ieee802-ethernet-interface@2019-06-21.yang"
|
||||
"infix-ethernet-interface@2024-02-27.yang"
|
||||
"infix-factory-default@2023-06-28.yang"
|
||||
@@ -53,7 +53,7 @@ MODULES=(
|
||||
"ietf-netconf-acm@2018-02-14.yang"
|
||||
"ietf-netconf@2013-09-29.yang -e writable-running -e candidate -e rollback-on-error -e validate -e startup -e url -e xpath -e confirmed-commit"
|
||||
"ietf-truststore@2023-12-28.yang -e central-truststore-supported -e certificates"
|
||||
"ietf-keystore@2023-12-28.yang -e central-keystore-supported -e inline-definitions-supported -e asymmetric-keys -e symmetric-keys"
|
||||
"ietf-keystore@2023-12-28.yang -e central-keystore-supported -e asymmetric-keys -e symmetric-keys"
|
||||
"ietf-tls-server@2023-12-28.yang -e server-ident-raw-public-key -e server-ident-x509-cert"
|
||||
|
||||
)
|
||||
|
||||
@@ -8,16 +8,36 @@ module infix-services {
|
||||
}
|
||||
import ietf-inet-types {
|
||||
prefix inet;
|
||||
reference
|
||||
"RFC 6991: Common YANG Data Types";
|
||||
}
|
||||
import ietf-crypto-types {
|
||||
prefix ct;
|
||||
reference
|
||||
"RFC 9640: YANG Data Types and Groupings for Cryptography";
|
||||
}
|
||||
|
||||
import ietf-keystore {
|
||||
prefix ks;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "Infix services, generic.";
|
||||
|
||||
revision 2024-12-03 {
|
||||
description "Add support for SSH server configuration";
|
||||
reference "internal";
|
||||
}
|
||||
revision 2024-12-02 {
|
||||
description "Expand mdns options: domain, allow/deny interfaces, reflector.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
revision 2024-06-08 {
|
||||
description "Add support for RESTCONF enable/disable as a web service.";
|
||||
reference "internal";
|
||||
}
|
||||
revision 2024-05-30 {
|
||||
description "Add support for RESTCONF enable/disable as a web service.";
|
||||
reference "internal";
|
||||
@@ -104,6 +124,48 @@ module infix-services {
|
||||
}
|
||||
}
|
||||
}
|
||||
container ssh {
|
||||
description "Configuration for the SSH daemon";
|
||||
leaf enabled {
|
||||
description "Enable or disable SSH daemon";
|
||||
type boolean;
|
||||
must ". = 'false' or (count(../listen) > 0 and count(../hostkey) > 0)" {
|
||||
error-message "Must be at least one listen address and a private key.";
|
||||
}
|
||||
}
|
||||
|
||||
leaf-list hostkey {
|
||||
description "Reference to asymmetric key in central keystore.
|
||||
|
||||
The hostkey can be shared with NETCONF, by default 'genkey' is used.";
|
||||
must "not(deref(.)/../ks:public-key-format) or "
|
||||
+ "(derived-from-or-self(deref(.)/../ks:public-key-format, 'ct:ssh-public-key-format') and"
|
||||
+ "derived-from-or-self(deref(.)/../ks:private-key-format, 'ct:rsa-private-key-format'))" {
|
||||
error-message "Only RSA hostkeys are supported.";
|
||||
}
|
||||
type ks:asymmetric-key-ref;
|
||||
}
|
||||
|
||||
list listen {
|
||||
key name;
|
||||
leaf name {
|
||||
type string;
|
||||
}
|
||||
|
||||
leaf address {
|
||||
description "Local IP address to listen on for inbound SSH connections.
|
||||
|
||||
INADDR_ANY (0.0.0.0) or INADDR6_ANY (0:0:0:0:0:0:0:0 a.k.a. ::) MUST be
|
||||
used when the server is to listen on all IPv4 or
|
||||
IPv6 addresses, respectively.";
|
||||
type inet:ip-address;
|
||||
}
|
||||
leaf port {
|
||||
description "Local port for SSH daemon to listen on.";
|
||||
type inet:port-number;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
container web {
|
||||
description "Web services";
|
||||
|
||||
Reference in New Issue
Block a user