From 3cce7ae82c47cb2276292f949af323d397f9fcbe Mon Sep 17 00:00:00 2001 From: sidey79 <7968127+sidey79@users.noreply.github.com> Date: Mon, 23 Dec 2024 01:02:31 +0100 Subject: [PATCH] test[entry.bats]: Added basic tests and improved script --- src/entry.sh | 257 +++++++++++++++++++++++++------------- src/tests/bats/entry.bats | 178 ++++++++++++++++++++++++++ 2 files changed, 350 insertions(+), 85 deletions(-) create mode 100644 src/tests/bats/entry.bats diff --git a/src/entry.sh b/src/entry.sh index ed4abfa..2ff9c00 100755 --- a/src/entry.sh +++ b/src/entry.sh @@ -1,94 +1,181 @@ #!/bin/bash -export ALEXAFHEM_DIR="/alexa-fhem" -export ALEXAFHEM_UID="${ALEXAFHEM_UID:-6062}" -export ALEXAFHEM_GID="${ALEXAFHEM_GID:-6062}" - -[ ! -f /image_info.EMPTY ] && touch /image_info.EMPTY - -chmod --quiet go-w ${ALEXAFHEM_DIR} - -# creating user environment -echo "Preparing user environment ..." -[ ! -s /etc/passwd.orig ] && cp -f /etc/passwd /etc/passwd.orig -[ ! -s /etc/shadow.orig ] && cp -f /etc/shadow /etc/shadow.orig -[ ! -s /etc/group.orig ] && cp -f /etc/group /etc/group.orig -cp -f /etc/passwd.orig /etc/passwd -cp -f /etc/shadow.orig /etc/shadow -cp -f /etc/group.orig /etc/group -groupadd --force --gid ${ALEXAFHEM_GID} alexa-fhem 2>&1>/dev/null -useradd --home ${ALEXAFHEM_DIR} --shell /bin/bash --uid ${ALEXAFHEM_UID} --no-create-home --no-user-group --non-unique alexa-fhem 2>&1>/dev/null -usermod --append --gid ${ALEXAFHEM_GID} --groups ${ALEXAFHEM_GID} alexa-fhem 2>&1>/dev/null -chown --recursive --quiet --no-dereference ${ALEXAFHEM_UID}:${ALEXAFHEM_GID} ${ALEXAFHEM_DIR}/ 2>&1>/dev/null - -# SSH key: Ed25519 -mkdir -p ${ALEXAFHEM_DIR}/.ssh -if [ ! -s ${ALEXAFHEM_DIR}/.ssh/id_ed25519 ]; then - echo -e " - Generating SSH Ed25519 client certificate for user 'alexa-fhem' ..." - rm -f ${ALEXAFHEM_DIR}/.ssh/id_ed25519* - ssh-keygen -t ed25519 -f ${ALEXAFHEM_DIR}/.ssh/id_ed25519 -q -N "" -o -a 100 - sed -i "s/root@.*/alexa-fhem@alexa-fhem-docker/" ${ALEXAFHEM_DIR}/.ssh/id_ed25519.pub -fi -chmod 600 ${ALEXAFHEM_DIR}/.ssh/id_ed25519 -chmod 644 ${ALEXAFHEM_DIR}/.ssh/id_ed25519.pub - -# SSH key: RSA -if [ ! -s ${ALEXAFHEM_DIR}/.ssh/id_rsa ]; then - echo -e " - Generating SSH RSA client certificate for user 'alexa-fhem' ..." - rm -f ${ALEXAFHEM_DIR}/.ssh/id_rsa* - ssh-keygen -t rsa -b 4096 -f ${ALEXAFHEM_DIR}/.ssh/id_rsa -q -N "" -o -a 100 - sed -i "s/root@.*/alexa-fhem@alexa-fhem-docker/" ${ALEXAFHEM_DIR}/.ssh/id_rsa.pub -fi -chmod 600 ${ALEXAFHEM_DIR}/.ssh/id_rsa -chmod 644 ${ALEXAFHEM_DIR}/.ssh/id_rsa.pub - -# SSH client hardening -if [ ! -f ${ALEXAFHEM_DIR}/.ssh/config ]; then -echo "IdentityFile ~/.ssh/id_ed25519 +#--- Global behaviour settings --------------------------------------------------------------------------------------- + +set -u # Make use of unbound variables an error +set -o pipefail # Distribute an error exit status through the whole pipe + +#--- Constants ------------------------------------------------------------------------------------------------------- +declare -r DEBUG=true + +#--- Environment variables, configurable from outside ---------------------------------------------------------------- + +declare -r ALEXAFHEM_DIR="${ALEXAFHEM_DIR:-/alexa-fhem}" +declare -ri ALEXAFHEM_UID="${ALEXAFHEM_UID:-6062}" +declare -ri ALEXAFHEM_GID="${ALEXAFHEM_GID:-6062}" + + + +prepare_user_environment() { + [ ! -f /image_info.EMPTY ] && touch /image_info.EMPTY + chmod --quiet go-w "${ALEXAFHEM_DIR}" + echo "Preparing user environment ..." + [ ! -s /etc/passwd.orig ] && cp -f /etc/passwd /etc/passwd.orig + [ ! -s /etc/shadow.orig ] && cp -f /etc/shadow /etc/shadow.orig + [ ! -s /etc/group.orig ] && cp -f /etc/group /etc/group.orig + cp -f /etc/passwd.orig /etc/passwd + cp -f /etc/shadow.orig /etc/shadow + cp -f /etc/group.orig /etc/group + groupadd --force --gid ${ALEXAFHEM_GID} alexa-fhem 2>&1>/dev/null + useradd --home "${ALEXAFHEM_DIR}" --shell /bin/bash --uid ${ALEXAFHEM_UID} --no-create-home --no-user-group --non-unique alexa-fhem 2>&1>/dev/null + usermod --append --gid ${ALEXAFHEM_GID} --groups ${ALEXAFHEM_GID} alexa-fhem 2>&1>/dev/null + chown --recursive --quiet --no-dereference ${ALEXAFHEM_UID}:${ALEXAFHEM_GID} "${ALEXAFHEM_DIR}"/ 2>&1>/dev/null +} + +generate_ssh_keys() { + mkdir -p ${ALEXAFHEM_DIR}/.ssh + if [ ! -s ${ALEXAFHEM_DIR}/.ssh/id_ed25519 ]; then + echo -e " - Generating SSH Ed25519 client certificate for user 'alexa-fhem' ..." + rm -f ${ALEXAFHEM_DIR}/.ssh/id_ed25519* + ssh-keygen -t ed25519 -f ${ALEXAFHEM_DIR}/.ssh/id_ed25519 -q -N "" -o -a 100 + sed -i "s/root@.*/alexa-fhem@alexa-fhem-docker/" ${ALEXAFHEM_DIR}/.ssh/id_ed25519.pub + fi + chmod 600 ${ALEXAFHEM_DIR}/.ssh/id_ed25519 + chmod 644 ${ALEXAFHEM_DIR}/.ssh/id_ed25519.pub + + if [ ! -s ${ALEXAFHEM_DIR}/.ssh/id_rsa ]; then + echo -e " - Generating SSH RSA client certificate for user 'alexa-fhem' ..." + rm -f ${ALEXAFHEM_DIR}/.ssh/id_rsa* + ssh-keygen -t rsa -b 4096 -f ${ALEXAFHEM_DIR}/.ssh/id_rsa -q -N "" -o -a 100 + sed -i "s/root@.*/alexa-fhem@alexa-fhem-docker/" ${ALEXAFHEM_DIR}/.ssh/id_rsa.pub + fi + chmod 600 ${ALEXAFHEM_DIR}/.ssh/id_rsa + chmod 644 ${ALEXAFHEM_DIR}/.ssh/id_rsa.pub +} + +harden_ssh_client() { + if [ ! -f ${ALEXAFHEM_DIR}/.ssh/config ]; then + echo "IdentityFile ~/.ssh/id_ed25519 IdentityFile ~/.ssh/id_rsa Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr HostKeyAlgorithms ssh-ed25519,ssh-rsa KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256 MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,umac-128-etm@openssh.com -" > ${ALEXAFHEM_DIR}/.ssh/config -fi - -# SSH key pinning -touch ${ALEXAFHEM_DIR}/.ssh/known_hosts -cat ${ALEXAFHEM_DIR}/.ssh/known_hosts /ssh_known_hosts.txt | grep -v ^# | sort -u -k2,3 > ${ALEXAFHEM_DIR}/.ssh/known_hosts.tmp -mv -f ${ALEXAFHEM_DIR}/.ssh/known_hosts.tmp ${ALEXAFHEM_DIR}/.ssh/known_hosts -chown -R alexa-fhem.alexa-fhem ${ALEXAFHEM_DIR}/.ssh/ - -if [[ ! -L /alexa-fhem/.alexa/config.json && -f /alexa-fhem/.alexa/config.json && ! -e /alexa-fhem/config.json ]]; then - echo " - Moving configuration from /alexa-fhem/.alexa/config.json to /alexa-fhem/config.json ..." - mv -f /alexa-fhem/.alexa/config.json /alexa-fhem/config.json -fi - -if [[ -e /alexa-fhem/alexa-fhem.json && ! -e /alexa-fhem/config.json ]]; then - echo " - Moving configuration from /alexa-fhem/alexa-fhem.json to /alexa-fhem/config.json ..." - mv -f /alexa-fhem/alexa-fhem.json /alexa-fhem/config.json +" > "${ALEXAFHEM_DIR}"/.ssh/config + fi +} + +pin_ssh_keys() { + touch ${ALEXAFHEM_DIR}/.ssh/known_hosts + cat ${ALEXAFHEM_DIR}/.ssh/known_hosts /ssh_known_hosts.txt | grep -v ^# | sort -u -k2,3 > ${ALEXAFHEM_DIR}/.ssh/known_hosts.tmp + mv -f ${ALEXAFHEM_DIR}/.ssh/known_hosts.tmp ${ALEXAFHEM_DIR}/.ssh/known_hosts + chown -R ${ALEXAFHEM_UID}:${ALEXAFHEM_GID} ${ALEXAFHEM_DIR}/.ssh/ +} + + +move_configurations() { + if [[ ! -L "${ALEXAFHEM_DIR}"/.alexa/config.json && -f "${ALEXAFHEM_DIR}"/.alexa/config.json && ! -e "${ALEXAFHEM_DIR}"/config.json ]]; then + echo " - Moving configuration from ${ALEXAFHEM_DIR}/.alexa/config.json to ${ALEXAFHEM_DIR}/config.json ..." + mv -f "${ALEXAFHEM_DIR}"/.alexa/config.json "${ALEXAFHEM_DIR}"/config.json + fi + + if [[ -e "${ALEXAFHEM_DIR}"/alexa-fhem.json && ! -e "${ALEXAFHEM_DIR}"/config.json ]]; then + echo " - Moving configuration from ${ALEXAFHEM_DIR}/alexa-fhem.json to ${ALEXAFHEM_DIR}/config.json ..." + mv -f "${ALEXAFHEM_DIR}"/alexa-fhem.json "${ALEXAFHEM_DIR}"/config.json + fi + + if [ ! -e "${ALEXAFHEM_DIR}"/config.json ]; then + echo " - Creating default config in ${ALEXAFHEM_DIR}/config.json ..." + cp /alexa-fhem.src/alexa-fhem-docker.config.json "${ALEXAFHEM_DIR}"/config.json + fi + + if [[ ! -L "${ALEXAFHEM_DIR}"/config.json && -f "${ALEXAFHEM_DIR}"/config.json ]]; then + echo " - Creating symlink to ${ALEXAFHEM_DIR}/config.json in ${ALEXAFHEM_DIR}/.alexa/config.json ..." + mkdir -p "${ALEXAFHEM_DIR}"/.alexa/ + ln -sf "${ALEXAFHEM_DIR}"/config.json "${ALEXAFHEM_DIR}"/.alexa/config.json + fi + ln -sf "${ALEXAFHEM_DIR}"/config.json "${ALEXAFHEM_DIR}"/alexa-fhem.json +} + + + +# Funktion, um Debug-Ausgaben zu machen +debug_log() { + if [ "$DEBUG" = true ]; then + echo "Debug: $1" >&2 + fi +} + +# Funktion, um den Pfad in einen `jq`-kompatiblen Suchstring umzuwandeln +convert_path_to_jq() { + local path="$1" + IFS='.' read -r -a parts <<< "$path" + local jq_path="" + for part in "${parts[@]}"; do + if [[ "$part" =~ ^[0-9]+$ ]]; then + jq_path+="[$part]" + else + jq_path+=".$part" + fi + done + echo "$jq_path" +} + + +# Funktion, um die JSON-Datei zu aktualisieren + +update_config() { + local config_file=$1 + + debug_log "update_config called with config_file=${config_file}" + + # JSON Datei in eine Variable laden + json=$(cat "$config_file") + + debug_log "initial JSON=${json}" + + # Iteriere über alle Umgebungsvariablen + while IFS= read -r var; do + path=$(echo "$var" | awk -F= '{print $1}' | tr '_' '.') + value=$(echo "$var" | awk -F= '{print substr($0, index($0,$2))}') + + debug_log "processing variable CONFIG_${path} with value=${value}" + + # Erzeuge den `jq`-Suchstring und setze den Wert in Anführungszeichen + jq_path=$(convert_path_to_jq "$path") + jq_search_string="$jq_path |= \"$value\"" + + debug_log "jq search string: $jq_search_string" + + # JSON Struktur basierend auf Umgebungsvariablen aktualisieren + json=$(echo "$json" | jq "$jq_search_string") + + done < <(env | grep '^CONFIG_' | sed 's/^CONFIG_//') + + debug_log "JSON after update=${json}" + + # JSON Datei speichern + if echo "$json" | jq empty > /dev/null 2>&1; then + echo "$json" | jq . > "$config_file" + debug_log "final JSON written to ${config_file}" + else + debug_log "Final JSON is invalid, not written to ${config_file}" + fi +} + +if [ "$#" -eq 1 ] && [ "$1" = "start" ]; then + prepare_user_environment + generate_ssh_keys + harden_ssh_client + pin_ssh_keys + move_configurations + + echo -e '\n\n' + if [ -s /pre-start.sh ]; then + echo "Running pre-start script ..." + /pre-start.sh + fi + echo 'Starting alexa-fhem ...' + su - alexa-fhem -c "cd "${ALEXAFHEM_DIR}"; /usr/local/bin/alexa-fhem --dockerDetached" fi - -if [ ! -e /alexa-fhem/config.json ]; then - echo " - Creating default config in /alexa-fhem/config.json ..." - cp /alexa-fhem.src/alexa-fhem-docker.config.json /alexa-fhem/config.json -fi - -if [[ ! -L /alexa-fhem/config.json && -f /alexa-fhem/config.json ]]; then - echo " - Creating symlink to config.json in /alexa-fhem/.alexa/config.json ..." - mkdir -p /alexa-fhem/.alexa/ - ln -sf ../config.json /alexa-fhem/.alexa/config.json -fi -ln -sf config.json alexa-fhem.json - -# Start main process -echo -e '\n\n' - -if [ -s /pre-start.sh ]; then - echo "Running pre-start script ..." - /pre-start.sh -fi - -echo 'Starting alexa-fhem ...' -su - alexa-fhem -c "cd "${ALEXAFHEM_DIR}"; /usr/local/bin/alexa-fhem --dockerDetached" diff --git a/src/tests/bats/entry.bats b/src/tests/bats/entry.bats new file mode 100644 index 0000000..a5c77a4 --- /dev/null +++ b/src/tests/bats/entry.bats @@ -0,0 +1,178 @@ +#!/usr/bin/env bats + +setup() { + + load '/opt/bats/test_helper/bats-support/load.bash' + load '/opt/bats/test_helper/bats-assert/load.bash' + load '/opt/bats/test_helper/bats-file/load.bash' + #load '/opt/bats/test_helper/bats-mock/load.bash' + + set -a + source /entry.sh + set +a + + mkdir -p "${ALEXAFHEM_DIR}" +} + +setup_file() { + + bats_require_minimum_version 1.5.0 + + export ALEXAFHEM_DIR="/tmp/alexa-fhem" + export ALEXAFHEM_GID=6062 + export ALEXAFHEM_UID=6062 + export DEBUG=false + + mkdir -p /alexa-fhem.src + +} + +teardown() { + rm -rf ${ALEXAFHEM_DIR}/* + + sleep 0 +} + +teardown_file() { + rm -rf ${ALEXAFHEM_DIR} + + sleep 0 +} + + +@test "Test prepare_user_environment function" { + + + run -0 prepare_user_environment + assert_file_exists /image_info.EMPTY +} + +@test "Test pin_ssh_keys function" { + mkdir -p ${ALEXAFHEM_DIR}/.ssh + touch "${ALEXAFHEM_DIR}"/.ssh/known_hosts + + assert_file_exists "/ssh_known_hosts.txt" + run -0 pin_ssh_keys + + assert_file_not_exists "${ALEXAFHEM_DIR}"/.ssh/known_hosts.tmp + assert_file_exists "${ALEXAFHEM_DIR}"/.ssh/known_hosts + + assert_file_contains "${ALEXAFHEM_DIR}"/.ssh/known_hosts "fhem-va.fhem.de" +} + +@test "Test harden_ssh_client function" { + run -0 harden_ssh_client + + assert_file_exists "${ALEXAFHEM_DIR}"/.ssh/config + assert_file_contains "${ALEXAFHEM_DIR}"/.ssh/config "IdentityFile" + assert_file_contains "${ALEXAFHEM_DIR}"/.ssh/config "Ciphers" + assert_file_contains "${ALEXAFHEM_DIR}"/.ssh/config "ssh-ed25519,ssh-rsa" +} + + + +@test "Move config.json from .alexa to ${ALEXAFHEM_DIR}/alexa-fhem" { + + mkdir -p "${ALEXAFHEM_DIR}"/.alexa + + echo "{My test file}" > "${ALEXAFHEM_DIR}"/.alexa/config.json + run -0 move_configurations + + assert_file_exists "${ALEXAFHEM_DIR}"/config.json + assert_output --partial "Moving configuration from ${ALEXAFHEM_DIR}/.alexa/config.json to ${ALEXAFHEM_DIR}/config.json" + assert_file_contains "${ALEXAFHEM_DIR}"/config.json "My test file" + + assert_link_exists "${ALEXAFHEM_DIR}"/.alexa/config.json +} + + +@test "Move alexa-fhem.json to ${ALEXAFHEM_DIR}/config.json" { + echo "{My test file from volume}" > "${ALEXAFHEM_DIR}"/alexa-fhem.json + run -0 move_configurations + + assert_output --partial "Moving configuration from ${ALEXAFHEM_DIR}/alexa-fhem.json to ${ALEXAFHEM_DIR}/config.json ..." + assert_file_contains "${ALEXAFHEM_DIR}"/config.json "My test file from volume" +} + + + +@test "Create default config in ${ALEXAFHEM_DIR}/config.json" { + echo "{CONFIG FROM DOCKER IMAGE}" > /alexa-fhem.src/alexa-fhem-docker.config.json + + run -0 move_configurations + + assert_file_exists "${ALEXAFHEM_DIR}"/config.json + assert_output --partial "Creating default config in ${ALEXAFHEM_DIR}/config.json ..." + assert_file_contains "${ALEXAFHEM_DIR}"/config.json "CONFIG FROM DOCKER IMAGE" + assert_file_exists /alexa-fhem.src/alexa-fhem-docker.config.json +} + +@test "Create symlink to ${ALEXAFHEM_DIR}/config.json in ${ALEXAFHEM_DIR}/.alexa" { + echo "{MY LINKED CONFIG FILE}" > "${ALEXAFHEM_DIR}"/config.json + run -0 move_configurations + + assert_output --partial "Creating symlink to ${ALEXAFHEM_DIR}/config.json in ${ALEXAFHEM_DIR}/.alexa/config.json ..." + assert_link_exists "${ALEXAFHEM_DIR}"/.alexa/config.json + assert_link_exists "${ALEXAFHEM_DIR}"/alexa-fhem.json + + assert_symlink_to "${ALEXAFHEM_DIR}"/config.json "${ALEXAFHEM_DIR}"/.alexa/config.json + assert_symlink_to "${ALEXAFHEM_DIR}"/config.json "${ALEXAFHEM_DIR}"/alexa-fhem.json + + assert_file_contains "${ALEXAFHEM_DIR}"/config.json "MY LINKED CONFIG FILE" +} + +@test "Test update_connections function overwrite key" { + move_configurations + + export CONFIG_connections_0_server='my.server.io' + + run -0 update_config "${ALEXAFHEM_DIR}"/config.json + + run -0 jq -e '.connections[0].server == "my.server.io"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.connections[0].server != "fhem"' "${ALEXAFHEM_DIR}"/config.json + + echo "#" >&3 && cat "${ALEXAFHEM_DIR}"/config.json >&3 +} + +@test "Test update_connections function add new key" { + move_configurations + #export DEBUG=true + + export CONFIG_connections_0_ssl="true" + run -0 update_config "${ALEXAFHEM_DIR}"/config.json + + run -0 jq -e '.connections[0].ssl == "true"' "${ALEXAFHEM_DIR}"/config.json + echo "#" >&3 && cat "${ALEXAFHEM_DIR}"/config.json >&3 +} + + +@test "Test update_config function update and add multipke keys" { + prepare_user_environment + move_configurations + export DEBUG=false + + export CONFIG_alexa_port=4000 + export CONFIG_alexa_name="myServer" + export CONFIG_alexa_ssl=true + export CONFIG_connections_0_port='8088' + export CONFIG_connections_0_filter='alexaName=..*' + export CONFIG_connections_0_server='fhem.docker.local' + export CONFIG_connections_0_ssl=true + export CONFIG_sshproxy_description='my special ssl client' + export CONFIG_sshproxy_ssh='/usr/bin/ssh2' + #export CONFIG_sshproxy_special='special Value' + + run -0 update_config "${ALEXAFHEM_DIR}"/config.json + echo "#" >&3 && cat "${ALEXAFHEM_DIR}"/config.json >&3 + + assert_file_permission 755 "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.alexa.port == "4000"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.alexa.name == "myServer"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.alexa.ssl == "true"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.connections[0].port == "8088"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.connections[0].filter == "alexaName=..*"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.connections[0].server == "fhem.docker.local"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.connections[0].ssl == "true"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.sshproxy.description == "my special ssl client"' "${ALEXAFHEM_DIR}"/config.json + run -0 jq -e '.sshproxy.ssh == "/usr/bin/ssh2"' "${ALEXAFHEM_DIR}"/config.json +}