From ca9daef15af8732fb2087b7ab3dbcc5fcf9d72e7 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Fri, 6 Oct 2023 14:18:41 +0200 Subject: [PATCH] confd: add support for generating /cfg/failure-config.cfg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- src/confd/Makefile.am | 1 + src/confd/bin/.confdrc | 28 +++++ src/confd/bin/bootstrap | 101 ++++++++++++----- src/confd/bin/gen-interfaces | 105 +++++++++++++++++- src/confd/confdrc | 34 ++++++ src/confd/configure.ac | 4 +- src/confd/share/Makefile.am | 4 +- .../10-authentication.json | 0 .../share/{factory => factory.d}/10-nacm.json | 0 .../10-netconf-server.json | 0 .../{factory => factory.d}/10-system.json | 0 src/confd/share/factory.d/Makefile.am | 3 + src/confd/share/factory/Makefile.am | 2 - .../10-authentication.json | 0 src/confd/share/failure.d/10-nacm.json | 1 + .../share/failure.d/10-netconf-server.json | 1 + .../{failure => failure.d}/10-system.json | 0 src/confd/share/failure.d/Makefile.am | 4 + src/confd/share/failure/10-nacm.json | 1 - .../share/failure/10-netconf-server.json | 1 - src/confd/share/failure/Makefile.am | 2 - 21 files changed, 251 insertions(+), 41 deletions(-) create mode 100644 src/confd/bin/.confdrc create mode 100644 src/confd/confdrc rename src/confd/share/{factory => factory.d}/10-authentication.json (100%) rename src/confd/share/{factory => factory.d}/10-nacm.json (100%) rename src/confd/share/{factory => factory.d}/10-netconf-server.json (100%) rename src/confd/share/{factory => factory.d}/10-system.json (100%) create mode 100644 src/confd/share/factory.d/Makefile.am delete mode 100644 src/confd/share/factory/Makefile.am rename src/confd/share/{failure => failure.d}/10-authentication.json (100%) create mode 120000 src/confd/share/failure.d/10-nacm.json create mode 120000 src/confd/share/failure.d/10-netconf-server.json rename src/confd/share/{failure => failure.d}/10-system.json (100%) create mode 100644 src/confd/share/failure.d/Makefile.am delete mode 120000 src/confd/share/failure/10-nacm.json delete mode 120000 src/confd/share/failure/10-netconf-server.json delete mode 100644 src/confd/share/failure/Makefile.am diff --git a/src/confd/Makefile.am b/src/confd/Makefile.am index 4962e440..e3731a3b 100644 --- a/src/confd/Makefile.am +++ b/src/confd/Makefile.am @@ -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 diff --git a/src/confd/bin/.confdrc b/src/confd/bin/.confdrc new file mode 100644 index 00000000..b783e426 --- /dev/null +++ b/src/confd/bin/.confdrc @@ -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" diff --git a/src/confd/bin/bootstrap b/src/confd/bin/bootstrap index a36bb737..cccc3fb5 100755 --- a/src/confd/bin/bootstrap +++ b/src/confd/bin/bootstrap @@ -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 diff --git a/src/confd/bin/gen-interfaces b/src/confd/bin/gen-interfaces index 79a4c74b..8d80a0aa 100755 --- a/src/confd/bin/gen-interfaces +++ b/src/confd/bin/gen-interfaces @@ -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 < '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 <