Browse Source

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 <valentin.lab@kalysto.org>
pull/1/head
Valentin Lab 4 years ago
parent
commit
2bac396242
  1. 3
      rsync-backup-target/README.org
  2. 29
      rsync-backup-target/build/entrypoint.sh
  3. 3
      rsync-backup-target/build/src/etc/sudoers.d/rsync
  4. 15
      rsync-backup-target/build/src/usr/local/sbin/ssh-admin-cmd-validate
  5. 22
      rsync-backup-target/build/src/usr/local/sbin/ssh-key
  6. 11
      rsync-backup-target/build/src/usr/local/sbin/ssh-update-keys
  7. 82
      rsync-backup-target/hooks/init

3
rsync-backup-target/README.org

@ -30,7 +30,8 @@ rsync-backup-target:
# - "10023:22" # - "10023:22"
options: options:
admin: ## These keys are for the allowed rsync-backup to write stuff with rsync 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\ ZDXGbvEVnTg16/yWqBGQg0QZdDjISsPn7D3Zr64g2qgD9n7EZghfGP9TkitvfrBYx8p\
7JkkUyt8nxklwOlKZFD5b3PF2bHloSsmjnP8ZMp5Ar7E+tn1guGrCrTcFIebpVGR3qF\ 7JkkUyt8nxklwOlKZFD5b3PF2bHloSsmjnP8ZMp5Ar7E+tn1guGrCrTcFIebpVGR3qF\
hRN9AlWNR+ekWo88ZlLJIrqD26jbWRJZm4nPCgqwhJwfHE3aVwfWGOqjSp4ij+jr2ac\ hRN9AlWNR+ekWo88ZlLJIrqD26jbWRJZm4nPCgqwhJwfHE3aVwfWGOqjSp4ij+jr2ac\

29
rsync-backup-target/build/entrypoint.sh

@ -12,6 +12,35 @@ RSYNC_HOME=/var/lib/rsync
mkdir -p "$RSYNC_HOME/.ssh" 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 ssh-update-keys
## Give back PID 1 so that ssh can receive signals ## Give back PID 1 so that ssh can receive signals

3
rsync-backup-target/build/src/etc/sudoers.d/rsync

@ -2,4 +2,5 @@
## the real check is done on the ``ssh-cmd-validate`` side. ## 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/bin/rsync --server * . /var/mirror/*
rsync ALL=(root) NOPASSWD: /usr/local/sbin/ssh-key *
%rsync ALL=(root) NOPASSWD: /usr/local/sbin/ssh-key *

15
rsync-backup-target/build/src/usr/local/sbin/ssh-admin-cmd-validate

@ -21,6 +21,15 @@ log() {
log "NEW ADMIN CONNECTION" 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() { reject() {
log "REJECTED: $SSH_ORIGINAL_COMMAND" log "REJECTED: $SSH_ORIGINAL_COMMAND"
# echo "ORIG: $SSH_ORIGINAL_COMMAND" >&2 # 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}) ssh_args=(${SSH_ORIGINAL_COMMAND})
# echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 # 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 elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key ls"$ ]]; then
log "ACCEPTED: $SSH_ORIGINAL_COMMAND" log "ACCEPTED: $SSH_ORIGINAL_COMMAND"
@ -52,7 +61,7 @@ elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key ls"$ ]]; then
ssh_args=(${SSH_ORIGINAL_COMMAND}) ssh_args=(${SSH_ORIGINAL_COMMAND})
# echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 # 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 elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key rm "[a-zA-Z0-9._-]+$ ]]; then
log "ACCEPTED: $SSH_ORIGINAL_COMMAND" 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}) ssh_args=(${SSH_ORIGINAL_COMMAND})
# echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 # 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 else
log "NOT MATCHING ANY ALLOWED COMMAND" log "NOT MATCHING ANY ALLOWED COMMAND"

22
rsync-backup-target/build/src/usr/local/sbin/ssh-key

@ -27,8 +27,8 @@ DARKWHITE="${ANSI_ESC}0;37m"
ssh-key-ls() { 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 [ -e "$f" ] || continue
ident=${f##*/} ident=${f##*/}
ident=${ident%.pub} ident=${ident%.pub}
@ -41,9 +41,9 @@ ssh-key-ls() {
ssh-key-rm() { 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 if ! [ -e "$delete" ]; then
echo "Error: key '$ident' not found." >&2 echo "Error: key '$ident' not found." >&2
return 1 return 1
@ -55,9 +55,9 @@ ssh-key-rm() {
ssh-key-add() { 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 echo "Error: expecting ssh-rsa key type" >&2
return 1 return 1
} }
@ -65,10 +65,10 @@ ssh-key-add() {
## ident are unique by construction (they are struct keys) ## ident are unique by construction (they are struct keys)
## but keys need to be also unique ## but keys need to be also unique
declare -A keys declare -A keys
mkdir -p "${RSYNC_KEY_PATH}/backup"
mkdir -p "${RSYNC_KEY_PATH}/backup/$label"
content="$type $key $email" content="$type $key $email"
ident="${email##*@}" ident="${email##*@}"
target="${RSYNC_KEY_PATH}/backup/$ident.pub"
target="${RSYNC_KEY_PATH}/backup/$label/$ident.pub"
if [ -e "$target" ]; then if [ -e "$target" ]; then
old_content=$(cat "$target") old_content=$(cat "$target")
if [ "$content" == "$old_content" ]; then if [ "$content" == "$old_content" ]; then
@ -76,6 +76,9 @@ ssh-key-add() {
return 0 return 0
fi fi
echo "Replacing key for '$ident'." >&2 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 fi
echo "$content" > "$target" echo "$content" > "$target"
@ -103,6 +106,3 @@ case "$1" in
;; ;;
esac esac

11
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 touch "$RSYNC_HOME"/.ssh/authorized_keys.new
for f in "$KEYS"/backup/*.pub; do
for f in "$KEYS"/backup/*/*.pub; do
[ -e "$f" ] || continue [ -e "$f" ] || continue
content=$(cat "$f") content=$(cat "$f")
ident="${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" 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 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,} mv "$RSYNC_HOME"/.ssh/authorized_keys{.new,}
chown rsync:rsync -R "$RSYNC_HOME"/.ssh -R chown rsync:rsync -R "$RSYNC_HOME"/.ssh -R

82
rsync-backup-target/hooks/init

@ -15,48 +15,64 @@ set -e
service_def=$(get_compose_service_def "$SERVICE_NAME") 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 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 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 "\ init-config-add "\
$SERVICE_NAME: $SERVICE_NAME:
volumes:
- $host_path_key:$local_path_key
labels: 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
Loading…
Cancel
Save