From 1d2618dfd07311521f32fc2b5db7dcd5703be826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jake=20Buchholz=20G=C3=B6kt=C3=BCrk?= Date: Mon, 24 Jan 2022 19:07:35 -0800 Subject: [PATCH] * imds is now an executable script * numerous imds script enhancements, including -h/--help * check imds for secondary ipv4 & ipvs via ifupdown-ng executor * simplify shell source to things /etc/conf.d/ and /lib/tiny-cloud/ * move log function to /lib/tiny-cloud/common --- Makefile | 3 +- bin/imds | 147 ++++++++++++++++++++++++++++++ etc/conf.d/tiny-cloud | 3 - etc/init.d/tiny-cloud | 2 +- etc/init.d/tiny-cloud-early | 2 +- etc/init.d/tiny-cloud-final | 2 +- etc/network/interfaces.d/DEFAULT | 5 +- etc/network/interfaces.d/lo | 4 +- lib/mdev/nvme-ebs-links | 19 ++-- lib/mdev/vnic-eth-hotplug | 38 ++------ lib/tiny-cloud/aws/imds | 37 ++++---- lib/tiny-cloud/aws/mdev | 10 +- lib/tiny-cloud/azure/imds | 22 +++-- lib/tiny-cloud/common | 24 +++++ lib/tiny-cloud/gcp/imds | 22 ++--- lib/tiny-cloud/imds | 62 ------------- lib/tiny-cloud/init-common | 2 +- lib/tiny-cloud/init-early | 63 ++++++------- lib/tiny-cloud/init-final | 28 +++--- lib/tiny-cloud/init-main | 48 +++++----- lib/tiny-cloud/mdev | 40 ++++---- lib/tiny-cloud/network-interfaces | 13 ++- lib/tiny-cloud/oci/imds | 15 ++- sbin/imds-net-sync | 144 +++++++++++++++++++++++++++++ usr/libexec/ifupdown-ng/imds | 14 +++ 25 files changed, 510 insertions(+), 259 deletions(-) create mode 100755 bin/imds create mode 100644 lib/tiny-cloud/common delete mode 100644 lib/tiny-cloud/imds create mode 100755 sbin/imds-net-sync create mode 100755 usr/libexec/ifupdown-ng/imds diff --git a/Makefile b/Makefile index 03d2a5c..4129b3a 100644 --- a/Makefile +++ b/Makefile @@ -2,5 +2,4 @@ PREFIX?=/ .PHONY: install install: - cp -r etc $(PREFIX) - cp -r lib $(PREFIX) + cp -r bin etc lib sbin usr $(PREFIX) diff --git a/bin/imds b/bin/imds new file mode 100755 index 0000000..61b24a0 --- /dev/null +++ b/bin/imds @@ -0,0 +1,147 @@ +#!/bin/sh +# vim:set ts=4 et ft=sh: + +# Tiny Cloud - Instance MetaData Service client + +### configuration + +source /etc/conf.d/tiny-cloud + +### cloud-specific variables/functions + +unset \ + IMDS_HEADER \ + IMDS_URI \ + IMDS_QUERY \ + IMDS_HOSTNAME \ + IMDS_SSH_KEYS \ + IMDS_USERDATA \ + IMDS_NICS \ + IMDS_MAC \ + IMDS_IPV4 \ + IMDS_IPV6 \ + IMDS_IPV4_NET \ + IMDS_IPV6_NET \ + IMDS_IPV4_PREFIX \ + IMDS_IPV6_PREFIX +unset -f \ + _imds_token \ + _imds_header \ + _imds_nic_index \ + 2>/dev/null || true + +### default variables/functions + +CLOUD="${CLOUD:-unknown}" +IMDS_ENDPOINT="169.254.169.254" + +_imds_ssh_keys() { _imds "$IMDS_SSH_KEYS"; } +_imds_userdata() { _imds "$IMDS_USERDATA"; } + +### load cloud-specific variables and functions + +if [ ! -d /lib/tiny-cloud/"$CLOUD" ]; then + echo "ERROR: Unknown Cloud '$CLOUD'" >&2 + exit 1 +fi +source /lib/tiny-cloud/"$CLOUD"/imds + +### non-overrideable functions + +_imds() { + wget --quiet --timeout 1 --output-document - \ + --header "$(_imds_header)" \ + "http://$IMDS_ENDPOINT/$IMDS_URI/$1$IMDS_QUERY" +} + +imds() { + local cmd args key rv err=1 + if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + _imds_help + return + fi + while [ -n "$1" ]; do + cmd=_imds + args= + key="$1"; shift + case $key in + # error handling + -e) err=0; continue ;; # ignore + +e) err=1; continue ;; # return + # TODO: retry/deadline + # output control + +n) echo; continue ;; # insert newline + +s) echo -n " "; continue ;; # insert space + +t) echo -en "\t"; continue ;; # insert tab + # key aliasing + @hostname) args="$IMDS_HOSTNAME" ;; + @ssh-keys) cmd=_imds_ssh_keys ;; + @userdata) cmd=_imds_userdata ;; + @nics) args="$IMDS_NICS" ;; + @nic:*) + cmd=imds + args=$(_imds_nic_args $(echo "${key#@nic:}" | tr , ' ')) + ;; + # use key verbatim + *) args="$key" ;; + esac + # TODO: retry/deadline + "$cmd" $args 2>/dev/null + rv=$? + [ $err -eq 0 ] && continue + [ $rv = "0" ] || return $rv + done +} + +_imds_nic_args() { + local key nic + nic=$(_imds_nic_index "$1") || return 1 + if [ -z "$2" ]; then + echo "$IMDS_NICS/$nic" + return + fi + while [ -n "$2" ]; do + key="$2" + shift + case "$key" in + @mac) key="$IMDS_MAC" ;; + @ipv4) key="$IMDS_IPV4" ;; + @ipv6) key="$IMDS_IPV6" ;; + @ipv4-net) key="$IMDS_IPV4_NET" ;; + @ipv6-net) key="$IMDS_IPV6_NET" ;; + @ipv4-prefix) key="$IMDS_IPV4_PREFIX" ;; + @ipv6-prefix) key="$IMDS_IPV6_PREFIX" ;; + # error/output control passthrough + -e|+[enst]) printf "$key\n"; continue ;; + esac + printf "$IMDS_NICS/$nic/$key\n" + done +} + +_imds_help() { + cat < | } ... + -h : help + -e / +e : ignore / catch errors + +n / +s / +t : insert newline / space / tab + :- + hostname : instance hostname + ssh-keys : instance SSH keys + userdata : instance user data + nics : instance NICs + nic:[, ...] : specific NIC interface + : network interface (i.e. eth1) + :- { -e | +e | +n | +s | +t | @ | } + :- + mac : mac address + ipv4 : ipv4 address(es) + ipv6 : ipv6 address(es) + ipv4-net : subnet ipv4 network(s) + ipv6-net : subnet ipv6 network(s) + ipv4-prefix : delegated ipv4 CIDR(s) + ipv6-prefix : delegated ipv6 CIDR(s) +EOT +} + + +imds "$@" diff --git a/etc/conf.d/tiny-cloud b/etc/conf.d/tiny-cloud index 760ac57..9f3dc84 100644 --- a/etc/conf.d/tiny-cloud +++ b/etc/conf.d/tiny-cloud @@ -13,9 +13,6 @@ # IMDS token validity, in seconds (AWS only) #IMDS_TOKEN_TTL=5 -# Location of libraries -#TINY_CLOUD_LIBS=/lib/tiny-cloud - # Location of var directory #TINY_CLOUD_VAR=/var/lib/cloud diff --git a/etc/init.d/tiny-cloud b/etc/init.d/tiny-cloud index acbcc3d..2a0222d 100755 --- a/etc/init.d/tiny-cloud +++ b/etc/init.d/tiny-cloud @@ -1,5 +1,5 @@ #!/sbin/openrc-run -# vim:set ft=sh noet ts=4: +# vim:set ts=8 noet ft=sh: description="Tiny Cloud Bootstrap - main phase" diff --git a/etc/init.d/tiny-cloud-early b/etc/init.d/tiny-cloud-early index e2e6e78..4cbe46d 100755 --- a/etc/init.d/tiny-cloud-early +++ b/etc/init.d/tiny-cloud-early @@ -1,5 +1,5 @@ #!/sbin/openrc-run -# vim:set ft=sh noet ts=4: +# vim:set ts=8 noet ft=sh: description="Tiny Cloud Bootstrap - early phase" diff --git a/etc/init.d/tiny-cloud-final b/etc/init.d/tiny-cloud-final index 01dd43c..24f1b43 100755 --- a/etc/init.d/tiny-cloud-final +++ b/etc/init.d/tiny-cloud-final @@ -1,5 +1,5 @@ #!/sbin/openrc-run -# vim:set ft=sh noet ts=4: +# vim:set ts=8 noet ft=sh: description="Tiny Cloud Bootstrap - final phase" diff --git a/etc/network/interfaces.d/DEFAULT b/etc/network/interfaces.d/DEFAULT index 0c99d69..aae1d09 100644 --- a/etc/network/interfaces.d/DEFAULT +++ b/etc/network/interfaces.d/DEFAULT @@ -1,2 +1,5 @@ auto %% -iface %% inet dhcp +iface %% + use dhcp + use imds + imds-resync 300 diff --git a/etc/network/interfaces.d/lo b/etc/network/interfaces.d/lo index f1bd92e..53c1f60 100644 --- a/etc/network/interfaces.d/lo +++ b/etc/network/interfaces.d/lo @@ -1,2 +1,4 @@ auto lo -iface lo inet loopback +iface lo + use link + use loopback \ No newline at end of file diff --git a/lib/mdev/nvme-ebs-links b/lib/mdev/nvme-ebs-links index 5b30edf..e407bab 100755 --- a/lib/mdev/nvme-ebs-links +++ b/lib/mdev/nvme-ebs-links @@ -1,25 +1,21 @@ #!/bin/sh # vim:set ts=2 et: +source /lib/tiny-cloud/common + # nvme tool not installed? -[ -x /usr/sbin/nvme ] || exit - -PROC="$(basename "$0")[$$]" - -log() { - FACILITY="kern.$1" - shift - logger -p "$FACILITY" -t "$PROC" "$@" -} +[ -x /usr/sbin/nvme ] || log crit "nvme cli not installed" raw_ebs_alias() { - /usr/sbin/nvme id-ctrl "/dev/$BASE" -b 2>/dev/null | dd bs=32 skip=96 count=1 2>/dev/null + /usr/sbin/nvme id-ctrl /dev/"$BASE" -b 2>/dev/null | + dd bs=32 skip=96 count=1 2>/dev/null } case $ACTION in add|"") BASE=$(echo "$MDEV" | sed -re 's/^(nvme[0-9]+n[0-9]+).*/\1/') PART=$(echo "$MDEV" | sed -re 's/nvme[0-9]+n[0-9]+p?//g') + # TODO: deadline instead of max tries MAXTRY=30 TRY=0 until [ -n "$EBS" ]; do @@ -42,7 +38,8 @@ case $ACTION in remove) for TARGET in sd* xvd* do - [ "$(readlink "$TARGET" 2>/dev/null)" = "$MDEV" ] && rm -f "$TARGET" && log notice "Removed $TARGET symlink for $MDEV" + [ $(readlink "$TARGET" 2>/dev/null) = "$MDEV" ] && rm -f "$TARGET" && \ + log notice "Removed $TARGET symlink for $MDEV" done ;; esac diff --git a/lib/mdev/vnic-eth-hotplug b/lib/mdev/vnic-eth-hotplug index 67d73d6..5ffea1b 100755 --- a/lib/mdev/vnic-eth-hotplug +++ b/lib/mdev/vnic-eth-hotplug @@ -1,29 +1,12 @@ #!/bin/sh -# vim: set ts=4 et: +# vim:set ts=4 et: set -e -PROC="$(basename "$0")[$$]" +source /lib/tiny-cloud/common -DEBUG= - -log() { - [ -z "$DEBUG" ] && [ "$1" = "debug" ] && return - local facility="kern.$1" - shift - logger -p "$facility" -t "$PROC" "$@" -} - -if [ -z "$MDEV" ]; then - log err "MDEV env not defined" - exit 1 -fi - -# where's the tiny-cloud lib directory? -if [ -z "$TINY_CLOUD_LIBS" ]; then - TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} - [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" - TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} +if [ -z "$MDEV" ] || [ -z "$ACTION" ]; then + log crit "MDEV or ACTION undefined, aborting" fi IFACE_CFG=/etc/network/interfaces @@ -46,19 +29,18 @@ ip() { assemble_interfaces() { log info "Rebuilding $IFACE_CFG" - # TODO: might be elsewhere, source /etc/conf.d/tiny-cloud - "$TINY_CLOUD_LIBS"/network-interfaces + /lib/tiny-cloud/network-interfaces } interface_up() { log info "Bringing up $MDEV" - # so /run/udhcpcd.$MDEV.pid isn't non-owner writeable + # umask so udhcpc PID file isn't non-owner writeable (umask 0022 && ifup "$MDEV") } cleanup_interface() { - local v p rtable="${MDEV#eth}" - let rtable+=1000 + local v pref rtable="${MDEV#eth}" + let rtable+=10000 log info "Cleaning up $MDEV" @@ -71,8 +53,8 @@ cleanup_interface() { # remove related rules for v in 4 6; do - for p in $(ip -"$v" rule show table "$rtable" | cut -d: -f1); do - ip -"$v" rule del pref "$p" + for pref in $(ip -"$v" rule show table "$rtable" | cut -d: -f1); do + ip -"$v" rule del pref "$pref" done done } diff --git a/lib/tiny-cloud/aws/imds b/lib/tiny-cloud/aws/imds index 17a3589..2501544 100644 --- a/lib/tiny-cloud/aws/imds +++ b/lib/tiny-cloud/aws/imds @@ -1,35 +1,40 @@ # AWS Instance MetaData Service variables and functions -# vim:set ft=sh noet ts=4: +# vim:set ts=4 et ft=sh: IMDS_HEADER="X-aws-ec2-metadata-token" IMDS_TOKEN_TTL_HEADER="X-aws-ec2-metadata-token-ttl-seconds" IMDS_TOKEN_TTL=${IMDS_TOKEN_TTL:-5} -IMDS_URL="http://169.254.169.254/latest" +IMDS_URI="latest" IMDS_HOSTNAME="meta-data/hostname" IMDS_SSH_KEYS="meta-data/public-keys" IMDS_USERDATA="user-data" +IMDS_NICS="meta-data/network/interfaces/macs" +IMDS_MAC="mac" +IMDS_IPV4="local-ipv4s" +IMDS_IPV6="ipv6s" +IMDS_IPV4_NET="subnet-ipv4-cidr-block" +IMDS_IPV6_NET="subnet-ipv6-cidr-blocks" +IMDS_IPV4_PREFIX="ipv4-prefix" +IMDS_IPV6_PREFIX="ipv6-prefix" + _imds_token() { - echo -ne "PUT /latest/api/token" \ - "HTTP/1.0\r\n$IMDS_TOKEN_TTL_HEADER: $IMDS_TOKEN_TTL\r\n\r\n" | - nc 169.254.169.254 80 | tail -n 1 + echo -ne "PUT /latest/api/token" \ + "HTTP/1.0\r\n$IMDS_TOKEN_TTL_HEADER: $IMDS_TOKEN_TTL\r\n\r\n" | + nc -w 1 "$IMDS_ENDPOINT" 80 | tail -n 1 } _imds_header() { - echo "$IMDS_HEADER: $(_imds_token)" + echo "$IMDS_HEADER: $(_imds_token)" } # digs deeper than the default -imds_ssh_keys() { - local key - - for key in $(imds "$IMDS_SSH_KEYS"); do - imds "$IMDS_SSH_KEYS/${key%=*}/openssh-key" - done | sort -u +_imds_ssh_keys() { + local key + for key in $(imds "$IMDS_SSH_KEYS"); do + imds "$IMDS_SSH_KEYS/${key%=*}/openssh-key" + done | sort -u } -# ignore 404s from IMDS, if no user-data is set -imds_userdata() { - _imds "$IMDS_USERDATA" || true -} +_imds_nic_index() { cat "/sys/class/net/$1/address"; } diff --git a/lib/tiny-cloud/aws/mdev b/lib/tiny-cloud/aws/mdev index 40d108e..a5cc02b 100644 --- a/lib/tiny-cloud/aws/mdev +++ b/lib/tiny-cloud/aws/mdev @@ -1,11 +1,11 @@ # AWS mdev Hotplug Modules -# vim:set ft=sh ts=4 noet: +# vim:set ts=4 et ft=sh: # makes symlinks for NVMe devices that correlate to AWS EBS sd/xvd devices mod__nvme_ebs_links() { - # nvme-cli not installed? - [ -x /usr/sbin/nvme ] || return 1 + # nvme-cli not installed? + [ -x /usr/sbin/nvme ] || return 1 - install_before '^nvme\.\*' \ - 'nvme[0-9]+n.* root:disk 0660 */lib/mdev/nvme-ebs-links' + install_before '^nvme\.\*' \ + 'nvme[0-9]+n.* root:disk 0660 */lib/mdev/nvme-ebs-links' } \ No newline at end of file diff --git a/lib/tiny-cloud/azure/imds b/lib/tiny-cloud/azure/imds index 82b2b87..b04dc79 100644 --- a/lib/tiny-cloud/azure/imds +++ b/lib/tiny-cloud/azure/imds @@ -1,28 +1,30 @@ # Azure Instance MetaData Service variables and functions -# vim:set ft=sh noet ts=4: +# vim:set ts=4 et ft=sh: IMDS_HEADER="Metadata" IMDS_QUERY="?format=text&api-version=2021-05-01" -IMDS_URL="http://169.254.169.254/metadata/instance" +IMDS_URI="metadata/instance" IMDS_HOSTNAME="compute/name" IMDS_SSH_KEYS="compute/publicKeys" IMDS_USERDATA="compute/userData" +IMDS_NICS="" + _imds_header() { - echo "$IMDS_HEADER: true" + echo "$IMDS_HEADER: true" } # dig deeper than default -imds_ssh_keys() { - local key +_imds_ssh_keys() { + local key - for key in $(imds "$IMDS_SSH_KEYS"); do - imds "$IMDS_SSH_KEYS/${key}/keyData" - done | sort -u + for key in $(imds "$IMDS_SSH_KEYS"); do + imds "$IMDS_SSH_KEYS/${key}/keyData" + done | sort -u } # decode userdata value -imds_userdata() { - imds "$IMDS_USERDATA" | base64 -d +_imds_userdata() { + imds "$IMDS_USERDATA" | base64 -d } \ No newline at end of file diff --git a/lib/tiny-cloud/common b/lib/tiny-cloud/common new file mode 100644 index 0000000..2590796 --- /dev/null +++ b/lib/tiny-cloud/common @@ -0,0 +1,24 @@ +# Tiny Cloud - common script functions +# vim: ts=4 et ft=sh: + +log() { + local facility=kern + local stderr + local tag=$(basename "$0") + while [ "${1:0:1}" = '-' ]; do + case "$1" in + -f) facility="$2"; shift ;; + -s) stderr=-s ;; + -t) tag="$tag/$2"; shift ;; + esac + shift + done + local level="$1" + [ -z "$DEBUG" ] && [ "$level" = debug ] && return + shift + + logger $stderr -p "$facility.$level" -t "$tag" "$@" + case "$level" in + crit|alert|emerg) exit 1 ;; + esac +} \ No newline at end of file diff --git a/lib/tiny-cloud/gcp/imds b/lib/tiny-cloud/gcp/imds index 7acfefa..5259d7e 100644 --- a/lib/tiny-cloud/gcp/imds +++ b/lib/tiny-cloud/gcp/imds @@ -1,26 +1,26 @@ # Google Cloud Instance MetaData Service variables and functions -# vim:set ft=sh noet ts=4: +# vim:set ts=4 et ft=sh: IMDS_HEADER="Metadata-Flavor" -IMDS_URL="http://169.254.169.254/computeMetadata/v1" +IMDS_URI="computeMetadata/v1" IMDS_HOSTNAME="instance/hostname" IMDS_SSH_KEYS=" - project/attributes/ssh-keys - instance/attributes/ssh-keys + project/attributes/ssh-keys + instance/attributes/ssh-keys " IMDS_USERDATA="instance/attributes/user-data" _imds_header() { - echo "$IMDS_HEADER: Google" + echo "$IMDS_HEADER: Google" } # merge project and instance keys -imds_ssh_keys() { - local ssh_keys +_imds_ssh_keys() { + local ssh_keys - for ssh_keys in $IMDS_SSH_KEYS; do - # ignore errors and strip leading ':' - _imds "$ssh_keys" | cut -d: -f2- - done | sort -u + for ssh_keys in $IMDS_SSH_KEYS; do + # ignore errors and strip leading ':' + imds -e "$ssh_keys" | cut -d: -f2- + done | sort -u } \ No newline at end of file diff --git a/lib/tiny-cloud/imds b/lib/tiny-cloud/imds deleted file mode 100644 index 405ae1b..0000000 --- a/lib/tiny-cloud/imds +++ /dev/null @@ -1,62 +0,0 @@ -# Tiny Cloud - Instance MetaData Service functions and variables -# vim:set ft=sh ts=4 noet: - -### configuration - -if [ -z "$TINY_CLOUD_LIBS" ]; then - TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} - [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" - TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} -fi - -# defaults -CLOUD="${CLOUD:-unknown}" - -# these are always cloud-specific - -unset \ - IMDS_HEADER \ - IMDS_QUERY \ - IMDS_URL \ - IMDS_HOSTNAME \ - IMDS_SSH_KEYS \ - IMDS_USERDATA - -unset -f \ - _imds_token \ - _imds_header \ - 2>/dev/null || true - -# without error reporting -_imds() { - wget --quiet --header "$(_imds_header)" --output-document - \ - "$IMDS_URL/$1$IMDS_QUERY" -} - -imds() { - _imds "$1" - RV=$? - [ $RV != 0 ] && echo "ERROR: $RV" >&2 - return $RV -} - -imds_hostname() { - imds "$IMDS_HOSTNAME" -} - -imds_ssh_keys() { - imds "$IMDS_SSH_KEYS" -} - -imds_userdata() { - imds "$IMDS_USERDATA" -} - -### load cloud-specific things - -if [ ! -d "$TINY_CLOUD_LIBS/$CLOUD" ]; then - echo "ERROR: Unknown Cloud '$CLOUD'" >&2 - exit 1 -fi - -source "$TINY_CLOUD_LIBS/$CLOUD/imds" diff --git a/lib/tiny-cloud/init-common b/lib/tiny-cloud/init-common index be9f7ca..d7ca324 100644 --- a/lib/tiny-cloud/init-common +++ b/lib/tiny-cloud/init-common @@ -1,5 +1,5 @@ # Tiny Cloud - Common Initialization -# vim:set ft=sh ts=4 noet: +# vim:set ts=4 et ft=sh: # set defaults CLOUD_USER=${CLOUD_USER:-alpine} diff --git a/lib/tiny-cloud/init-early b/lib/tiny-cloud/init-early index 2011a19..65551ee 100644 --- a/lib/tiny-cloud/init-early +++ b/lib/tiny-cloud/init-early @@ -1,49 +1,44 @@ # Tiny Cloud - Early Phase Functions -# vim:set ft=sh ts=4 noet: +# vim:set ts=4 et ft=sh: -if [ -z "$TINY_CLOUD_LIBS" ]; then - TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} - [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" - TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} -fi - -source "$TINY_CLOUD_LIBS"/init-common +source /etc/conf.d/tiny-cloud +source /lib/tiny-cloud/init-common expand_root() { - # explicitly use busybox, in case util-linux is also installed - local mountpoint=$(busybox mountpoint -n / | cut -d' ' -f1) - local volume=$(echo "$mountpoint" | - sed -Ee "s/(nvme\d+n\d|(xv|s)d[a-z])p?\d?$/\1/" - ) - local partition + # explicitly use busybox, in case util-linux is also installed + local mountpoint=$(busybox mountpoint -n / | cut -d' ' -f1) + local volume=$(echo "$mountpoint" | + sed -Ee "s/(nvme\d+n\d|(xv|s)d[a-z])p?\d?$/\1/" + ) + local partition - if [ "$mountpoint" != "$volume" ]; then - # it's a partition, resize it - partition=$(echo "$mountpoint" | sed -Ee "s/.*(\d+)$/\1/") - echo ", +" | sfdisk -q --no-reread -N "$partition" "$volume" - partx -u "$volume" - fi - # resize filesystem - mount -orw,remount / - resize2fs "$mountpoint" + if [ "$mountpoint" != "$volume" ]; then + # it's a partition, resize it + partition=$(echo "$mountpoint" | sed -Ee "s/.*(\d+)$/\1/") + echo ", +" | sfdisk -q --no-reread -N "$partition" "$volume" + partx -u "$volume" + fi + # resize filesystem + mount -orw,remount / + resize2fs "$mountpoint" } -assemble_interfaces() { "$TINY_CLOUD_LIBS"/network-interfaces; } +assemble_interfaces() { /lib/tiny-cloud/network-interfaces; } has_cloud_hotplugs() { [ -n "$HOTPLUG_MODULES" ]; } install_hotplugs() { - local result + local result - for module in $HOTPLUG_MODULES; do - result='-' - echo -n " $module" - if type "mod__$module" | grep -q "is a function"; then - "mod__$module" && result='+' || result='!' - fi - echo -n "($result)" - done + for module in $HOTPLUG_MODULES; do + result='-' + echo -n " $module" + if type "mod__$module" | grep -q "is a function"; then + "mod__$module" && result='+' || result='!' + fi + echo -n "($result)" + done } HOTPLUG_TYPE=${HOTPLUG_TYPE:-mdev} -source "$TINY_CLOUD_LIBS/$HOTPLUG_TYPE" \ No newline at end of file +source /lib/tiny-cloud/"$HOTPLUG_TYPE" \ No newline at end of file diff --git a/lib/tiny-cloud/init-final b/lib/tiny-cloud/init-final index 32351ab..222e19a 100644 --- a/lib/tiny-cloud/init-final +++ b/lib/tiny-cloud/init-final @@ -1,30 +1,24 @@ # Tiny Cloud - Final Phase Functions -# vim:set ft=sh ts=4 noet: +# vim:set ts=4 et ft=sh: -if [ -z "$TINY_CLOUD_LIBS" ]; then - TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} - [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" - TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} -fi - -source "$TINY_CLOUD_LIBS"/init-common -source "$TINY_CLOUD_LIBS"/imds +source /etc/conf.d/tiny-cloud +source /lib/tiny-cloud/init-common save_userdata() { - imds_userdata > "$TINY_CLOUD_VAR/$CLOUD_USERDATA" + imds -e @userdata > "$TINY_CLOUD_VAR/$CLOUD_USERDATA" } is_userdata_script() { - head -n1 "$TINY_CLOUD_VAR/$CLOUD_USERDATA" | grep -q '#!/' + head -n1 "$TINY_CLOUD_VAR/$CLOUD_USERDATA" | grep -q '#!/' } run_userdata() { - local log="$TINY_CLOUD_LOGS/$CLOUD_USERDATA.log" - local exit="$TINY_CLOUD_LOGS/$CLOUD_USERDATA.exit" - local userdata="$TINY_CLOUD_VAR/$CLOUD_USERDATA" + local log="$TINY_CLOUD_LOGS/$CLOUD_USERDATA.log" + local exit="$TINY_CLOUD_LOGS/$CLOUD_USERDATA.exit" + local userdata="$TINY_CLOUD_VAR/$CLOUD_USERDATA" - chmod +x "$userdata" - { "$userdata" 2>& 1; echo $? > "$exit"; } | tee "$log" + chmod +x "$userdata" + { "$userdata" 2>& 1; echo $? > "$exit"; } | tee "$log" - return $(cat "$exit") + return $(cat "$exit") } \ No newline at end of file diff --git a/lib/tiny-cloud/init-main b/lib/tiny-cloud/init-main index 972ce62..8bbc6dd 100644 --- a/lib/tiny-cloud/init-main +++ b/lib/tiny-cloud/init-main @@ -1,41 +1,35 @@ # Tiny Cloud - Main Phase Functions -# vim:set ft=sh ts=4 noet: +# vim:set ts=4 et ft=sh: -if [ -z "$TINY_CLOUD_LIBS" ]; then - TINY_CLOUD_CONF=${TINY_CLOUD_CONF:-/etc/conf.d/tiny-cloud} - [ -f "$TINY_CLOUD_CONF" ] && source "$TINY_CLOUD_CONF" - TINY_CLOUD_LIBS=${TINY_CLOUD_LIBS:-/lib/tiny-cloud} -fi - -source "$TINY_CLOUD_LIBS"/init-common -source "$TINY_CLOUD_LIBS"/imds +source /etc/conf.d/tiny-cloud +source /lib/tiny-cloud/init-common # ensure existence of output directories [ ! -d "$TINY_CLOUD_LOGS" ] && mkdir -p "$TINY_CLOUD_LOGS" [ ! -d "$TINY_CLOUD_VAR" ] && mkdir -p "$TINY_CLOUD_VAR" set_hostname() { - local fqdn=$(imds_hostname) - local host="${fqdn%%\.*}" - echo "$host" > /etc/hostname - hostname -F /etc/hostname - echo -e "127.0.1.1\t$fqdn $host" >> /etc/hosts + local fqdn=$(imds @hostname) + local host="${fqdn%%\.*}" + echo "$host" > /etc/hostname + hostname -F /etc/hostname + echo -e "127.0.1.1\t$fqdn $host" >> /etc/hosts } set_ssh_keys() { - local user="$CLOUD_USER" - local pwent=$(getent passwd "$user") - local group=$(echo "$pwent" | cut -d: -f4) - local ssh_dir="$(echo "$pwent" | cut -d: -f6)/.ssh" - local keys_file="$ssh_dir/authorized_keys" + local user="$CLOUD_USER" + local pwent=$(getent passwd "$user") + local group=$(echo "$pwent" | cut -d: -f4) + local ssh_dir="$(echo "$pwent" | cut -d: -f6)/.ssh" + local keys_file="$ssh_dir/authorized_keys" - if [ ! -d "$ssh_dir" ]; then - mkdir -p "$ssh_dir" - chmod 700 "$ssh_dir" - fi + if [ ! -d "$ssh_dir" ]; then + mkdir -p "$ssh_dir" + chmod 700 "$ssh_dir" + fi - touch "$keys_file" - chmod 600 "$keys_file" - chown -R "$user:$group" "$ssh_dir" - imds_ssh_keys > "$keys_file" + touch "$keys_file" + chmod 600 "$keys_file" + chown -R "$user:$group" "$ssh_dir" + imds @ssh-keys > "$keys_file" } \ No newline at end of file diff --git a/lib/tiny-cloud/mdev b/lib/tiny-cloud/mdev index bbc9930..63b79ad 100644 --- a/lib/tiny-cloud/mdev +++ b/lib/tiny-cloud/mdev @@ -1,35 +1,35 @@ # Tiny Cloud - mdev hotplug functions -# vim:set ft=sh ts=4 noet: +# vim:set ts=4 et ft=sh: # generic helper function to install mdev rules install_before() { - local before="$1" - shift - local line="$*" + local before="$1" + shift + local line="$*" - # already installed - fgrep -q "$line" /etc/mdev.conf && return 0 + # already installed + fgrep -q "$line" /etc/mdev.conf && return 0 - if grep -q "$before" /etc/mdev.conf; then - # install before existing rule - line="-$line" - else - # no rule exists, put it before the catch-all fallback - before='^# fallback' - line="$line\n" - fi - sed -i -Ee "s|($before.*)|$line\n\1|" /etc/mdev.conf + if grep -q "$before" /etc/mdev.conf; then + # install before existing rule + line="-$line" + else + # no rule exists, put it before the catch-all fallback + before='^# fallback' + line="$line\n" + fi + sed -i -Ee "s|($before.*)|$line\n\1|" /etc/mdev.conf } # hotpluggable VNICs (multi-cloud) mod__vnic_eth_hotplug() { - install_before '^eth' \ - 'eth[0-9] root:root 0644 */lib/mdev/vnic-eth-hotplug' + install_before '^eth' \ + 'eth[0-9] root:root 0644 */lib/mdev/vnic-eth-hotplug' - # NICs attached at launch don't get added with mdev -s - assemble_interfaces + # NICs attached at launch don't get added with mdev -s + assemble_interfaces } # load cloud-specific functions -[ -f "$TINY_CLOUD_LIBS/$CLOUD/mdev" ] && source "$TINY_CLOUD_LIBS/$CLOUD/mdev" \ No newline at end of file +[ -f /lib/tiny-cloud/"$CLOUD"/mdev ] && source /lib/tiny-cloud/"$CLOUD"/mdev \ No newline at end of file diff --git a/lib/tiny-cloud/network-interfaces b/lib/tiny-cloud/network-interfaces index 4ca23d5..e2683f0 100755 --- a/lib/tiny-cloud/network-interfaces +++ b/lib/tiny-cloud/network-interfaces @@ -1,5 +1,5 @@ #!/bin/sh -# vim: set ts=4 et: +# vim:set ts=4 et: set -e @@ -15,10 +15,15 @@ cat > "$IFACE_CFG.new" < "$IFACE" - printf "%s\n\n" "$(cat "$IFACE")" >> "$IFACE_CFG.new" + case $IFACE in + lo|eth*) + [ ! -f "$IFACE" ] && sed -e "s/%%/$IFACE/g" DEFAULT > "$IFACE" + printf "%s\n\n" "$(cat "$IFACE")" >> "$IFACE_CFG.new" + ;; + *) continue ;; + esac done # all the rest diff --git a/lib/tiny-cloud/oci/imds b/lib/tiny-cloud/oci/imds index 1589aac..9c78e16 100644 --- a/lib/tiny-cloud/oci/imds +++ b/lib/tiny-cloud/oci/imds @@ -1,13 +1,22 @@ # OCI Instance MetaData Service variables and functions -# vim:set ft=sh noet ts=4: +# vim:set ts=4 et ft=sh: IMDS_HEADER="Authorization" -IMDS_URL="http://169.254.169.254/opc/v2" +IMDS_URI="opc/v2" IMDS_HOSTNAME="instance/hostname" IMDS_SSH_KEYS="instance/metadata/ssh_authorized_keys" IMDS_USERDATA="instance/metadata/userdata" _imds_header() { - echo "$IMDS_HEADER: Bearer Oracle" + echo "$IMDS_HEADER: Bearer Oracle" +} + +_imds_nic_index() { + local m n=0 + local mac=$(cat /sys/class/net/$1/mac) + while m=$(imds $IMDS_NICS/$n/mac | tr A-F a-f); do + [ "$m" = "$mac" ] && echo $n; return 0 + done + return 1 } diff --git a/sbin/imds-net-sync b/sbin/imds-net-sync new file mode 100755 index 0000000..a6db02c --- /dev/null +++ b/sbin/imds-net-sync @@ -0,0 +1,144 @@ +#!/bin/sh +# vim: ts=4 et ft=sh: + +# Sync interface's network configuration with IMDS + +[ -z "$VERBOSE" ] || set -x + +source /lib/tiny-cloud/common + +IFACE=${IFACE:-unknown} +[ "$IFACE" = unknown ] && log -s crit "IFACE not set, aborting" + +# kill interface's imds-net-sync daemon +[ "$1" = '-k' ] && PHASE=pre=down && shift + +PHASE=${PHASE:-post-up} + +# route table number +RTABLE=${IFACE#eth} +let RTABLE+=10000 + +# ip [+F] [-4|-6] [] +ip() { + local fail_ok v=-4 cmd level + [ "$1" = '+F' ] && fail_ok=1 && shift + if [ "$1" = '-4' ] || [ "$1" = '-6' ]; then + v="$1" + shift + fi + cmd="$2" + [ "$cmd" = show ] && level=debug || level=info + if /sbin/ip "$v" "$@" || [ -n "$fail_ok" ]; then + log -s "$level" "OK: ip $v $*" + else + log -s err "FAIL: ip $v $*" + fi +} + +# get secondary IPv4s currently on the interface +iface_ip4s() { + ip -4 addr show "$IFACE" secondary | + sed -E -e '/inet /!d' -e 's/.*inet ([0-9.]+).*/\1/' +} + +# get IPv6s currently on the interface +iface_ip6s() { + ip -6 addr show "$IFACE" scope global | + sed -E -e '/inet6/!d' -e 's/.*inet6 ([0-9a-f:]+).*/\1/' +} + +imds_ip4s() { + local ip4=$(imds "@nic:$IFACE,@ipv4") + local ip4s=$(echo "$ip4" | tail +2) # secondary IPv4s + local ip4p ip4_cidr ip4_gw + + # non-eth0 interfaces need custom route tables + # + if [ "$IFACE" != eth0 ] && [ -n "$ip4s" ] && + [ -z $(ip +F -4 route show table "$RTABLE" 2>/dev/null) ]; then + ip4p=$(echo "$ip4" | head -1) # primary IPv4 + ip4_cidr=$(imds "@nic:$IFACE,@ipv4-net") # TODO: get from iface instead? + # TODO: this may not hold true for non-AWS clouds + ip4_gw=$(echo "$ip4_cidr" | cut -d/ -f1 | + awk -F. '{ print $1"."$2"."$3"."$4+1 }') + ip -4 route add default via "$ip4_gw" dev "$IFACE" table "$RTABLE" + ip -4 route add "$ip4_cidr" dev "$IFACE" proto kernel scope link \ + src "$ip4p" table "$RTABLE" + fi + echo "$ip4s" +} + +imds_ip6s() { + local ip6s gw tries=20 + ip6s=$(imds "@nic:$IFACE,@ipv6") + + # non-eth0 interfaces need custom route tables + # + # NOTE: busybox iproute2 doesn't do 'route show table' properly for IPv6, + # so iproute2-minimal package is required! + # + if [ "$IFACE" != eth0 ] && [ -n "$ip6s" ] && + [ -z $(ip +F -6 route show table "$RTABLE" 2>/dev/null) ]; then + while true; do + gw=$(ip -6 route show dev "$IFACE" default | awk '{ print $3 }') + [ -n "$gw" ] && break + let tries-- + if [ "$tries" -eq 0 ]; then + log -s warn "Unable to get IPv6 gateway RA after 10s" + break + fi + sleep 0.5 + done + ip -6 route add default via "$gw" dev "$IFACE" table "$RTABLE" + fi + echo "$ip6s" +} + +in_list() { + echo "$2" | grep -q "^$1$" +} + +# ip_addr {4|6} {add|del} +ip_addr() { + local mask=32 # IPv4 always /32 + [ "$1" -eq 6 ] && mask=128 # IPv6 always /128 + ip -"$1" addr "$2" "$3/$mask" dev "$IFACE" + + # TODO: only non eth0? delegated ipv[46] prefixes? + [ "$IFACE" = eth0 ] && return + + # non-eth0 interfaces get rules associating IPs with route tables + ip -"$1" rule "$2" from "$3" lookup "$RTABLE" +} + +# sync_ips {4|6} "" "" +sync_ips() { + local i + # remove extra IPs + for i in $3; do + in_list "$i" "$2" || ip_addr "$1" del "$i" + done + # add missing IPs + for i in $2; do + in_list "$i" "$3" || ip_addr "$1" add "$i" + done +} + +imds_iface_sync() { + log -s info "SYNCING: $IFACE" + sync_ips 4 "$(imds_ip4s)" "$(iface_ip4s)" + sync_ips 6 "$(imds_ip6s)" "$(iface_ip6s)" + log -s info "FINISHED: $IFACE" +} + +case "$PHASE" in + post-up) + # TODO: daemonize this + imds_iface_sync + ;; + pre-down) + # TODO: kill daemon, maybe some cleanup + ;; + *) +esac \ No newline at end of file diff --git a/usr/libexec/ifupdown-ng/imds b/usr/libexec/ifupdown-ng/imds new file mode 100755 index 0000000..1001491 --- /dev/null +++ b/usr/libexec/ifupdown-ng/imds @@ -0,0 +1,14 @@ +#!/bin/sh +# vim: set ts=8 noet: + +# Tiny Cloud IMDS ifupdown-ng executor + +case "$PHASE" in +post-up) + /sbin/imds-net-sync + ;; +pre-down) + /sbin/imds-net-sync -k + ;; +*) ;; +esac