confd: add support for generating /cfg/failure-config.cfg

This rather huge change is a refactor of the factory-config generataion to add
support for also generating a failure-config.

The confd bootstrap script has been given an rc file.  This both eases manual
testing, when modifying the script(s), and also makes it easier to override
from a br2-external.  Infix default is router/end-device, but a br2-external
may be a switch firmware and want to default to all ports in a bridge.

The generated failure-config creates a fail-safe "do no harm" config to boot
with in case startup-config for some reason is broken or cannot be applied,
e.g., bug in confd.  Meaning, for both the router and switch use-cases the
device will start up with all interfaces isolated¹, with an IPv6 SLAAC (EUI64)
address per interface.

Services enabled in this fail-safe mode are: LLDP, mDNS/SD, SSH, and NETCONF.
All to facilitate diagnostics, troubleshooting and device recovery.

Other noteworthy changes include:

 - rename factory/failure directories again -> factory.d/failure.d.  Use
   same naming as we do on target for directories holding generated files
 - The bootstrap script no longer regenerates /cfg/factory.d on each boot
 - The bootstrap script copies all static templates to /cfg/factory.d in
   case a newer image changes the contents of them.  For troubleshooting
 - Support for overriding the 20-interfaces.json generation by br2-external
 - Support for additional 30-config.json (ovrride/extend) by br2-external
 - Expand gen-interfaces to support bridge use-case.
_____
¹ For a switch this means "no switchport", i.e., no switching between ports
  otherwise connected to a switchcore (or bridge) in startup-config.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2023-10-09 10:41:46 +02:00
committed by Tobias Waldekranz
parent 3e48af6c36
commit ca9daef15a
21 changed files with 251 additions and 41 deletions
+1
View File
@@ -1,5 +1,6 @@
SUBDIRS = src yang share
dist_doc_DATA = README.md LICENSE
dist_sysconf_DATA = confdrc
pkglibexec_SCRIPTS = $(wildcard bin/*)
DISTCLEANFILES = *~ *.d
ACLOCAL_AMFLAGS = -I m4
+28
View File
@@ -0,0 +1,28 @@
# Defaults for testing bootstrap script -*-conf-*-
TESTING=true
PATH=.:$PATH
INIT_DATA=/etc/sysrepo/factory-default.json
SEARCH=/usr/share/yang/modules/confd:/usr/share/yang/modules/libnetconf2:/usr/share/yang/modules/libyang:/usr/share/yang/modules/netopeer2:/usr/share/yang/modules/sysrepo
CFG_PATH_=/tmp/confd
FACTORY_DEFAULTS_D=../share/factory.d
FAILURE_DEFAULTS_D=../share/failure.d
FACTORY_D=$CFG_PATH_/factory.d
FAILURE_D=$CFG_PATH_/failure.d
FACTORY_CFG=$CFG_PATH_/factory-config.cfg
FAILURE_CFG=$CFG_PATH_/failure-config.cfg
STARTUP_CFG=$CFG_PATH_/startup-config.cfg
# Uncomment this line in to create a bridge (br0) with all (classified
# 'gruop port') interfaces as bridge ports. The br0 interface will get
# an IPv6 EUI64 SLAAC address as well as an IPv4 ZeroConf address, and a
# DHCPv4 address if the device is attached to a LAN with a DHCP server.
#GEN_IFACE_OPTS="-b -4 -d"
# Default hostname in Fail Secure mode, plus last three octets in the base
# MAC address, e.g. "failed-c0-ff-ee".
FAIL_HOSTNAME="failed"
+73 -28
View File
@@ -11,42 +11,87 @@
# It's all really simple ... this script takes care of 1-4
#
PATH=/usr/libexec/confd/:$PATH
INIT_DATA=/etc/sysrepo/factory-default.json
SEARCH=/usr/share/yang/modules/confd:/usr/share/yang/modules/libnetconf2:/usr/share/yang/modules/libyang:/usr/share/yang/modules/netopeer2:/usr/share/yang/modules/sysrepo
# /etc/confdrc controls the behavior or most of the gen-scripts,
# customize in an overlay when using Infix as an br2-external.
RC=/etc/confdrc
if [ "$1" = "-f" ] && [ -f "$2" ]; then
RC=$2
fi
[ "$FACTORY_D" ] || FACTORY_D=/usr/share/confd/factory
[ "$CFG_D" ] || CFG_D=/cfg
[ "$GENCFG_D" ] || GENCFG_D=/cfg/factory.d
if [ ! -f "$RC" ]; then
logger -sik -p user.error -t bootstrap "Missing rc file $RC" 2>/dev/null \
|| echo "Missing rc file $RC"
exit 99
fi
FACTORY=$CFG_D/factory-config.cfg
STARTUP=$CFG_D/startup-config.cfg
# shellcheck disable=SC1090
. "$RC"
# Generate device's factory-config at first boot
if [ ! -d "$GENCFG_D" ]; then
mkdir -p $GENCFG_D
FACTORY_GEN="$(dirname "$FACTORY_CFG")/$(basename "$FACTORY_CFG" .cfg).gen"
FAILURE_GEN="$(dirname "$FAILURE_CFG")/$(basename "$FAILURE_CFG" .cfg).gen"
gen-hostname >$GENCFG_D/20-auto-hostname.json
gen-interfaces >$GENCFG_D/20-auto-interfaces.json
# Generate device's factory-config at first boot or on override
if [ ! -d "$FACTORY_D" ] || [ "$FACTORY_OVERRIDE" = "true" ]; then
mkdir -p "$FACTORY_D"
[ -s $GENCFG_D/20-auto-hostkey.json ] || gen-hostkeys >$GENCFG_D/20-auto-hostkey.json
# Save the original templates (from the original image) used to create factory-config
for file in $(find "$FACTORY_DEFAULTS_D" -type f); do
cp "$file" "$FACTORY_D"
done
# Allow regenerating default factory-config, but keep it read-ony
# to prevent it from being overwritten by mistake by users.
rm -f $CFG_D/auto-factory-config.cfg
# Create an overlay for /etc/hostname to change the default in an br2-external
gen-hostname >"$FACTORY_D/20-hostname.json"
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"
rm -f "$FACTORY_GEN"
# shellcheck disable=SC2046
jq -s 'reduce .[] as $item ({}; . * $item)' \
$(find $FACTORY_D -name '*.json') \
$(find $GENCFG_D -name '*.json') \
>$CFG_D/auto-factory-config.cfg
chmod 444 $CFG_D/auto-factory-config.cfg
jq -s 'reduce .[] as $item ({}; . * $item)' \
$(find "$FACTORY_D" -name '*.json' | sort) >"$FACTORY_GEN"
chmod 444 "$FACTORY_GEN"
# TODO: Look for statically defined factory-config, based on the
# system's product ID, or just custom site-specific factory.
# If we haven't found a better one, settle for auto-factory-config as
# the system's factory-config.
[ -h $FACTORY ] || ln -sf auto-factory-config.cfg $FACTORY
# If we haven't found a more specific, better match, settle for
# factory-config.gen as the system's factory-config.
[ -h "$FACTORY_CFG" ] || ln -sf "$(basename "$FACTORY_GEN")" "$FACTORY_CFG"
fi
# Generate device's failure-config at first boot
if [ ! -d "$FAILURE_D" ] || [ "$FAILURE_OVERRIDE" = "true" ]; then
mkdir -p "$FAILURE_D"
# Save the original templates (from the original image) used to create failure-config
for file in $(find "$FAILURE_DEFAULTS_D" -type f); do
cp "$file" "$FAILURE_D"
done
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"
rm -f "$FAILURE_GEN"
# shellcheck disable=SC2046
jq -s 'reduce .[] as $item ({}; . * $item)' \
$(find "$FAILURE_D" -name '*.json' | sort) >"$FAILURE_GEN"
chmod 444 "$FAILURE_GEN"
[ -h "$FAILURE_CFG" ] || ln -sf "$(basename "$FAILURE_GEN")" "$FAILURE_CFG"
fi
if [ -n "$TESTING" ]; then
echo "Done."
exit 0
fi
# Drop all pre-initialized data from netopeer2 install, then re-create
@@ -54,7 +99,7 @@ fi
# and then we initialize it all with our factory defaults.
rm -rf /etc/sysrepo/* /dev/shm/sr_*
mkdir -p /etc/sysrepo/
cp "$FACTORY" "$INIT_DATA"
cp "$FACTORY_CFG" "$INIT_DATA"
sysrepoctl -s $SEARCH \
-i ietf-system@2014-08-06.yang -g wheel -p 0660 \
-e authentication \
@@ -138,8 +183,8 @@ sysrepoctl -c ietf-netconf-acm -g wheel -p 0660
# On first boot, install factory-config as startup-config. Due to a
# limitation in sysrepo we cannot initialize ietf-netconf-acm, so we
# cheat, see sysrepo#3079
if [ ! -f "$STARTUP" ]; then
sysrepocfg -f json -X"$STARTUP"
if [ ! -f "$STARTUP_CFG" ]; then
sysrepocfg -f json -X"$STARTUP_CFG"
fi
# Clear running-config so we can load startup in the next step
+102 -3
View File
@@ -1,7 +1,41 @@
#!/bin/sh
# Generates ietf-intefaces + ietf-ip settings for all detected interfaces
# Interfaces must be of type Ethernet (copper/sfp), not be "internal" DSA
# ports, and must have a "parentbus", i.e., be physical ports.
#
# Usage: gen-interfaces [-4bd]
#
# -b brN Bridged mode, set all interfaces as ports in brN
# -4 Generic option for both modes, enables ZeroConf IPv4
# -d Generic option for both modes, enables DHCPv4 client
# -g grp Filter interfaces using 'ip link show group grp'
#
# By default this script generates plain interfaces with IPv6 autoconfig
# (EUI64) enabled. Useful both in a plain router and a Fail Secure mode
# where any switchcore-backed ports should be prevented from switching.
#
# The default mode currently does not support the '-4' or '-d' options,
# for IPv4 ZeroConf or DHCPv4 address, beacause they require source
# routing, or similar, to work. IPv6 does not require that and is
# therefore the recommended access method. A separate (and optional)
# 'gen-cfg-custom' script is checked for in the bootstrap script which
# can be used to enable IPv4 and DHCP on a single service interface if
# needed.
#
# The -b option triggers the bridge mode, creating a 'brN' interface
# using all interfaces classified in 'group port' by the nameif script.
# In the bridge mode port interfaces do not have any IP address, the
# IPv6 autoconfig address is instead set on 'brN'. If the '-4' option
# is set, ZeroConfig (169.254.x.y) is anabled on 'brN'. If the '-d'
# option is set, a DHCPv4 client is enabled on 'brN'.
set -e
bridge=
ipv4=false
ipv6=true
dhcp=
gen_interface()
{
cat <<EOF
@@ -9,13 +43,45 @@ gen_interface()
"name": "$1",
"type": "infix-if-type:ethernet",
"ietf-ip:ipv6": {
"enabled": true
"enabled": $2
EOF
if [ -n "$3" ]; then
cat <<EOF
},
"infix-interfaces:bridge-port": {
"bridge": "$3"
EOF
fi
cat <<EOF
}
}
EOF
}
phys_ifaces=$(ip -d -j link show | jq -r '
while [ "$1" != "" ]; do
case $1 in
-4)
ipv4=true
;;
-b)
bridge="$2"
shift
ipv6=false # for bridge port interfaces
;;
-d)
dhcp=true
;;
-g)
group="group $2" # -g port => 'ip link show group port'
shift
;;
*)
break
esac
shift
done
phys_ifaces=$(ip -d -j link show $group | jq -r '
.[] |
select(.link_type == "ether") |
select(.group != "internal") |
@@ -35,9 +101,42 @@ cat <<EOF
"ietf-ip:ipv6": {
"address": [{ "ip": "::1", "prefix-length": 128 }]
}
EOF
if [ -n "$bridge" ]; then
cat <<EOF
},
{
"name": "$bridge",
"type": "infix-if-type:bridge",
"ietf-ip:ipv4": {
"infix-ip:autoconf": {
"enabled": $ipv4
}
},
"ietf-ip:ipv6": {
"enabled": true
}
EOF
fi
cat <<EOF
}
$(for iface in $phys_ifaces; do gen_interface $iface; done)
$(for iface in $phys_ifaces; do gen_interface $iface $ipv6 $bridge; done)
]
EOF
if [ "$dhcp" = "true" ] && [ -n "$bridge" ]; then
cat <<EOF
},
"infix-dhcp-client:dhcp-client": {
"enabled": true,
"client-if": [
{
"if-name": "$bridge"
}
]
EOF
fi
cat <<EOF
}
}
EOF
+34
View File
@@ -0,0 +1,34 @@
# Defaults for /usr/libexec/confd/bootstrap -*-conf-*-
# Copy and mofify this file to your br2-external overlay
PATH=/usr/libexec/confd/:$PATH
INIT_DATA=/etc/sysrepo/factory-default.json
SEARCH=/usr/share/yang/modules/confd:/usr/share/yang/modules/libnetconf2:/usr/share/yang/modules/libyang:/usr/share/yang/modules/netopeer2:/usr/share/yang/modules/sysrepo
CFG_PATH_=/cfg
FACTORY_DEFAULTS_D=/usr/share/confd/factory.d
FAILURE_DEFAULTS_D=/usr/share/confd/failure.d
FACTORY_D=$CFG_PATH_/factory.d
FAILURE_D=$CFG_PATH_/failure.d
FACTORY_CFG=$CFG_PATH_/factory-config.cfg
FAILURE_CFG=$CFG_PATH_/failure-config.cfg
STARTUP_CFG=$CFG_PATH_/startup-config.cfg
# Uncomment this line in to create a bridge (br0) with all (classified
# 'gruop port') interfaces as bridge ports. The br0 interface will get
# an IPv6 EUI64 SLAAC address as well as an IPv4 ZeroConf address, and a
# DHCPv4 address if the device is attached to a LAN with a DHCP server.
#GEN_IFACE_OPTS="-g port -b br0 -4 -d"
# Default hostname in Fail Secure mode, plus last three octets in the base
# MAC address, e.g. "failed-c0-ff-ee".
FAIL_HOSTNAME="failed"
# Uncomment to regenerate factory-config (except hostkeys) on each boot.
#FACTORY_OVERRIDE=true
# Uncomment to regenerate failure-config (except hostkeys) on each boot.
#FAILURE_OVERRIDE=true
+2 -2
View File
@@ -6,8 +6,8 @@ AM_SILENT_RULES(yes)
AC_CONFIG_FILES([
Makefile
share/Makefile
share/factory/Makefile
share/failure/Makefile
share/factory.d/Makefile
share/failure.d/Makefile
src/Makefile
yang/Makefile
])
+2 -2
View File
@@ -1,2 +1,2 @@
SUBDIRS = factory failure
pkgdata_DATA = $(wildcard share/*)
SUBDIRS = factory.d failure.d
dist_pkgdata_DATA = README
+3
View File
@@ -0,0 +1,3 @@
factorydir = $(pkgdatadir)/factory.d
dist_factory_DATA = 10-authentication.json 10-nacm.json \
10-netconf-server.json 10-system.json
-2
View File
@@ -1,2 +0,0 @@
factorydir = $(pkgdatadir)/factory
factory_DATA = $(wildcard *.json)
+1
View File
@@ -0,0 +1 @@
../factory.d/10-nacm.json
+1
View File
@@ -0,0 +1 @@
../factory.d/10-netconf-server.json
+4
View File
@@ -0,0 +1,4 @@
failuredir = $(pkgdatadir)/failure.d
dist_failure_DATA = 10-authentication.json 10-nacm.json \
10-netconf-server.json 10-system.json
-1
View File
@@ -1 +0,0 @@
../factory/10-nacm.json
@@ -1 +0,0 @@
../factory/10-netconf-server.json
-2
View File
@@ -1,2 +0,0 @@
failuredir = $(pkgdatadir)/failure
failure_DATA = $(wildcard *.json)