From 2bac3962425ae45844498ac4e2a4b14bec2999da Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Thu, 22 Apr 2021 18:51:27 +0200 Subject: [PATCH] new: [rsync-backup-target] backup groups allow key management delegation Each backup endpoint is now part of a backup group. Each backup group can be managed by ad-hoc and separate account. Signed-off-by: Valentin Lab --- rsync-backup-target/README.org | 3 +- rsync-backup-target/build/entrypoint.sh | 29 +++++++ .../build/src/etc/sudoers.d/rsync | 3 +- .../src/usr/local/sbin/ssh-admin-cmd-validate | 15 +++- .../build/src/usr/local/sbin/ssh-key | 22 ++--- .../build/src/usr/local/sbin/ssh-update-keys | 11 +-- rsync-backup-target/hooks/init | 82 +++++++++++-------- 7 files changed, 108 insertions(+), 57 deletions(-) diff --git a/rsync-backup-target/README.org b/rsync-backup-target/README.org index e41f456..01e65c1 100644 --- a/rsync-backup-target/README.org +++ b/rsync-backup-target/README.org @@ -30,7 +30,8 @@ rsync-backup-target: # - "10023:22" options: admin: ## These keys are for the allowed rsync-backup to write stuff with rsync - myadmin: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDESdz8bWtVcDQJ68IE/KpuZM9tAq\ + myadmin: + - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDESdz8bWtVcDQJ68IE/KpuZM9tAq\ ZDXGbvEVnTg16/yWqBGQg0QZdDjISsPn7D3Zr64g2qgD9n7EZghfGP9TkitvfrBYx8p\ 7JkkUyt8nxklwOlKZFD5b3PF2bHloSsmjnP8ZMp5Ar7E+tn1guGrCrTcFIebpVGR3qF\ hRN9AlWNR+ekWo88ZlLJIrqD26jbWRJZm4nPCgqwhJwfHE3aVwfWGOqjSp4ij+jr2ac\ diff --git a/rsync-backup-target/build/entrypoint.sh b/rsync-backup-target/build/entrypoint.sh index e06f68c..3966cf3 100755 --- a/rsync-backup-target/build/entrypoint.sh +++ b/rsync-backup-target/build/entrypoint.sh @@ -12,6 +12,35 @@ RSYNC_HOME=/var/lib/rsync mkdir -p "$RSYNC_HOME/.ssh" +if ! egrep '^[^:]+:x:101:101:' /etc/passwd; then + ## Then it is a first run of this container, users + ## need to be created. Notice that container will be + ## re-created anew if user config was changed. + for user_dir in /etc/rsync/keys/admin/*; do + [ -d "$user_dir" ] || continue + user="${user_dir##*/}" + [ "$user" != "rsync" ] || continue + + adduser -S "$user" -h "$user_dir" -G rsync && + chown "$user":rsync "$user_dir" || { + echo "Error: couldn't create user $user or chown '$user_dir'." >&2 + exit 1 + } + ## Without this, account is concidered locked by SSH + sed -ri "s/^$user:\!:/$user:*NP*:/g" /etc/shadow + + ## Withouth this, force-command will not run + sed -ri "s%^($user.*:)[^:]+$%\1/bin/bash%g" /etc/passwd + + done +fi + +log="/var/log/rsync/ssh-admin-cmd-validate.log" +touch "$log" +chown rsync:rsync "$log" +chmod g+rw "$log" + + ssh-update-keys ## Give back PID 1 so that ssh can receive signals diff --git a/rsync-backup-target/build/src/etc/sudoers.d/rsync b/rsync-backup-target/build/src/etc/sudoers.d/rsync index 433761d..fdf382a 100644 --- a/rsync-backup-target/build/src/etc/sudoers.d/rsync +++ b/rsync-backup-target/build/src/etc/sudoers.d/rsync @@ -2,4 +2,5 @@ ## the real check is done on the ``ssh-cmd-validate`` side. rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server * . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/local/sbin/ssh-key * + +%rsync ALL=(root) NOPASSWD: /usr/local/sbin/ssh-key * diff --git a/rsync-backup-target/build/src/usr/local/sbin/ssh-admin-cmd-validate b/rsync-backup-target/build/src/usr/local/sbin/ssh-admin-cmd-validate index 9f7b3b6..f5730f3 100755 --- a/rsync-backup-target/build/src/usr/local/sbin/ssh-admin-cmd-validate +++ b/rsync-backup-target/build/src/usr/local/sbin/ssh-admin-cmd-validate @@ -21,6 +21,15 @@ log() { log "NEW ADMIN CONNECTION" +if [ -z "$1" ] || ! [[ "$1" =~ ^[a-zA-Z0-9._-]+$ ]]; then + log "INVALID SETUP, ARG IS: '$1'" + echo "Your command has been rejected. Contact administrator." + exit 1 +fi + +label="$1" + + reject() { log "REJECTED: $SSH_ORIGINAL_COMMAND" # echo "ORIG: $SSH_ORIGINAL_COMMAND" >&2 @@ -43,7 +52,7 @@ if [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key add ssh-rsa "[a-zA-Z0-9/+]+" "[a-zA-Z ssh_args=(${SSH_ORIGINAL_COMMAND}) # echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 - exec sudo /usr/local/sbin/ssh-key "${ssh_args[@]:1}" + exec sudo /usr/local/sbin/ssh-key add "$label" "${ssh_args[@]:2}" elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key ls"$ ]]; then log "ACCEPTED: $SSH_ORIGINAL_COMMAND" @@ -52,7 +61,7 @@ elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key ls"$ ]]; then ssh_args=(${SSH_ORIGINAL_COMMAND}) # echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 - exec /usr/local/sbin/ssh-key "${ssh_args[@]:1}" + exec /usr/local/sbin/ssh-key ls "$label" "${ssh_args[@]:2}" elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key rm "[a-zA-Z0-9._-]+$ ]]; then log "ACCEPTED: $SSH_ORIGINAL_COMMAND" @@ -61,7 +70,7 @@ elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key rm "[a-zA-Z0-9._-]+$ ]]; then ssh_args=(${SSH_ORIGINAL_COMMAND}) # echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 - exec sudo /usr/local/sbin/ssh-key "${ssh_args[@]:1}" + exec sudo /usr/local/sbin/ssh-key rm "$label" "${ssh_args[@]:2}" else log "NOT MATCHING ANY ALLOWED COMMAND" diff --git a/rsync-backup-target/build/src/usr/local/sbin/ssh-key b/rsync-backup-target/build/src/usr/local/sbin/ssh-key index f569e51..bf8599b 100755 --- a/rsync-backup-target/build/src/usr/local/sbin/ssh-key +++ b/rsync-backup-target/build/src/usr/local/sbin/ssh-key @@ -27,8 +27,8 @@ DARKWHITE="${ANSI_ESC}0;37m" ssh-key-ls() { - local f content - for f in "${RSYNC_KEY_PATH}"/backup/*.pub; do + local label="$1" f content + for f in "${RSYNC_KEY_PATH}"/backup/"$label"/*.pub; do [ -e "$f" ] || continue ident=${f##*/} ident=${ident%.pub} @@ -41,9 +41,9 @@ ssh-key-ls() { ssh-key-rm() { - local ident="$1" delete + local label="$1" ident="$2" delete - delete="${RSYNC_KEY_PATH}/backup/$ident.pub" + delete="${RSYNC_KEY_PATH}/backup/$label/$ident.pub" if ! [ -e "$delete" ]; then echo "Error: key '$ident' not found." >&2 return 1 @@ -55,9 +55,9 @@ ssh-key-rm() { ssh-key-add() { - local type="$1" key="$2" email="$3" + local label="$1" type="$2" key="$3" email="$4" - [ "$1" == "ssh-rsa" ] || { + [ "$type" == "ssh-rsa" ] || { echo "Error: expecting ssh-rsa key type" >&2 return 1 } @@ -65,10 +65,10 @@ ssh-key-add() { ## ident are unique by construction (they are struct keys) ## but keys need to be also unique declare -A keys - mkdir -p "${RSYNC_KEY_PATH}/backup" + mkdir -p "${RSYNC_KEY_PATH}/backup/$label" content="$type $key $email" ident="${email##*@}" - target="${RSYNC_KEY_PATH}/backup/$ident.pub" + target="${RSYNC_KEY_PATH}/backup/$label/$ident.pub" if [ -e "$target" ]; then old_content=$(cat "$target") if [ "$content" == "$old_content" ]; then @@ -76,6 +76,9 @@ ssh-key-add() { return 0 fi echo "Replacing key for '$ident'." >&2 + elif [ -e "${RSYNC_KEY_PATH}/backup/"*"/$ident.pub" ]; then + echo "ident '$ident' is already reserved, please pick another one." >&2 + return 1 fi echo "$content" > "$target" @@ -103,6 +106,3 @@ case "$1" in ;; esac - - - diff --git a/rsync-backup-target/build/src/usr/local/sbin/ssh-update-keys b/rsync-backup-target/build/src/usr/local/sbin/ssh-update-keys index 2c2f2ef..be2e46e 100755 --- a/rsync-backup-target/build/src/usr/local/sbin/ssh-update-keys +++ b/rsync-backup-target/build/src/usr/local/sbin/ssh-update-keys @@ -16,7 +16,7 @@ mkdir -p "$RSYNC_HOME/.ssh" touch "$RSYNC_HOME"/.ssh/authorized_keys.new -for f in "$KEYS"/backup/*.pub; do +for f in "$KEYS"/backup/*/*.pub; do [ -e "$f" ] || continue content=$(cat "$f") ident="${f##*/}" @@ -28,13 +28,8 @@ for f in "$KEYS"/backup/*.pub; do echo "command=\"/usr/local/sbin/ssh-cmd-validate \\\"$ident\\\"\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $content" done >> "$RSYNC_HOME"/.ssh/authorized_keys.new -for f in "$KEYS"/admin/*.pub; do - [ -e "$f" ] || continue - content=$(cat "$f") - echo "command=\"/usr/local/sbin/ssh-admin-cmd-validate\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $content" -done >> "$RSYNC_HOME"/.ssh/authorized_keys.new - -mv "$RSYNC_HOME"/.ssh/authorized_keys{,.old} +[ -e ""$RSYNC_HOME"/.ssh/authorized_keys" ] && + mv "$RSYNC_HOME"/.ssh/authorized_keys{,.old} mv "$RSYNC_HOME"/.ssh/authorized_keys{.new,} chown rsync:rsync -R "$RSYNC_HOME"/.ssh -R diff --git a/rsync-backup-target/hooks/init b/rsync-backup-target/hooks/init index 3312663..475c711 100755 --- a/rsync-backup-target/hooks/init +++ b/rsync-backup-target/hooks/init @@ -15,48 +15,64 @@ set -e service_def=$(get_compose_service_def "$SERVICE_NAME") -keys=$(echo "$service_def" | shyaml -y get-value options.admin 2>/dev/null) || { - err "You must specify a ${WHITE}keys${NORMAL} struct to use this service" +admin_keys=$(echo "$service_def" | shyaml -y get-value options.admin 2>/dev/null) || { + err "You must specify a ${WHITE}admin${NORMAL} struct to use this service" exit 1 } -[ "$(echo "$keys" | shyaml -y get-type 2>/dev/null)" == "struct" ] || { - err "Invalid value type for ${WHITE}keys${NORMAL}, please provide a struct" +[ "$(echo "$admin_keys" | shyaml -y get-type 2>/dev/null)" == "struct" ] || { + err "Invalid value type for ${WHITE}admin${NORMAL}, please provide a struct" exit 1 } -local_path_key=/etc/rsync/keys -host_path_key="$SERVICE_DATASTORE${local_path_key}" - -## ident are unique by construction (they are struct keys) -## but keys need to be also unique -declare -A keys -while read-0 ident key; do - if [ "${keys[$key]}" ]; then - err "Duplicate key: key for ident '$ident' is same as ident '${keys["$key"]}'." - exit 1 - fi - if ! [[ "$ident" =~ ^[a-zA-Z0-9._-]+$ ]]; then - err "Invalid identifier '$ident'," \ - "please use only alphanumerical char, dots, dash or underscores." - exit 1 - fi - debug "Creating access key for ${ident}" || true - echo "$key" | file_put "$host_path_key/admin/${ident}.pub" - keys["$key"]="$ident" -done < <(echo "$keys" | shyaml key-values-0) - -debug "Adding config hash to enable recreating upon config change." -config_hash=$({ - ## XXXvlab: ``env -i`` sole purpose is to protect find - ## against big shell environments, and prevent it to fail. - env -i find "${host_path_key}/admin" \ - -name \*.pub -exec md5sum {} \; - } | md5_compat) || exit 1 + +rebuild-config() { + + rm -rf "$SERVICE_CONFIGSTORE/etc/rsync/keys/admin" + mkdir -p "$host_path_key" + + while read-0 ident keys; do + ident=$(e "$ident" | shyaml get-value) + if ! [[ "$ident" =~ ^[a-zA-Z0-9._-]+$ ]]; then + err "Invalid identifier '$ident'," \ + "please use only alphanumerical char, dots, dash or underscores." + exit 1 + fi + debug "Setting access keys for ${ident}" + [ "$(echo "$keys" | shyaml -y get-type 2>/dev/null)" == "sequence" ] || { + err "Invalid value type for ${WHITE}admin.$ident${NORMAL}, please provide a sequence" + echo " Received: '$keys'" >&2 + exit 1 + } + + while read-0 key; do + echo "command=\"/usr/local/sbin/ssh-admin-cmd-validate \\\"$ident\\\"\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty $key" + done < <(echo "$keys" | shyaml get-values-0) | file_put "$host_path_key/$ident/.ssh/authorized_keys" + done < <(echo "$admin_keys" | shyaml -y key-values-0) + + e "$control_users" > "$CONTROL_USERS_FILE" + +} + +local_path_key=/etc/rsync/keys/admin +host_path_key="$SERVICE_CONFIGSTORE${local_path_key}" init-config-add "\ $SERVICE_NAME: + volumes: + - $host_path_key:$local_path_key labels: - - compose.config_hash=$config_hash + - compose.config_hash=$control_users " +CONTROL_USERS_FILE="$SERVICE_DATASTORE/.control-pass" +## Was it already properly propagated to database ? +control_users=$(H "${admin_keys}" "$(declare -f "rebuild-config")") + + +if [ -e "$CONTROL_USERS_FILE" ] && [ "$control_users" == "$(cat "$CONTROL_USERS_FILE")" ]; then + exit 0 +fi + + +rebuild-config