Browse Source
new: [rsync-backup-target] allow dynamic management of backup keys
new: [rsync-backup-target] allow dynamic management of backup keys
Signed-off-by: Valentin Lab <valentin.lab@kalysto.org>upd-docker
Valentin Lab
4 years ago
10 changed files with 337 additions and 21 deletions
-
84rsync-backup-target/README.org
-
13rsync-backup-target/build/entrypoint.sh
-
1rsync-backup-target/build/src/etc/sudoers.d/rsync
-
79rsync-backup-target/build/src/usr/local/sbin/ssh-admin-cmd-validate
-
108rsync-backup-target/build/src/usr/local/sbin/ssh-key
-
41rsync-backup-target/build/src/usr/local/sbin/ssh-update-keys
-
11rsync-backup-target/hooks/init
-
16rsync-backup-target/hooks/log_rotate-relation-joined
-
1rsync-backup-target/metadata.yml
-
4rsync-backup-target/resources/bin/compose-add-rsync-key
@ -0,0 +1,84 @@ |
|||
#+PROPERTY: Effort_ALL 0 0:30 1:00 2:00 0.5d 1d 1.5d 2d 3d 4d 5d |
|||
#+PROPERTY: Max_effort_ALL 0 0:30 1:00 2:00 0.5d 1d 1.5d 2d 3d 4d 5d |
|||
#+PROPERTY: header-args:python :var filename=(buffer-file-name) |
|||
#+PROPERTY: header-args:sh :var filename=(buffer-file-name) |
|||
#+TODO: TODO WIP BLOCKED | DONE CANCELED |
|||
#+LATEX_HEADER: \usepackage[margin=0.5in]{geometry} |
|||
#+LaTeX_HEADER: \hypersetup{linktoc = all, colorlinks = true, urlcolor = DodgerBlue4, citecolor = PaleGreen1, linkcolor = blue} |
|||
#+LaTeX_CLASS: article |
|||
#+OPTIONS: H:8 ^:nil prop:("Effort" "Max_effort") tags:not-in-toc |
|||
#+COLUMNS: %50ITEM %Effort(Min Effort) %Max_effort(Max Effort) |
|||
|
|||
#+TITLE: rsync-backup-target |
|||
|
|||
#+LATEX: \pagebreak |
|||
|
|||
Usage of this service |
|||
|
|||
#+LATEX: \pagebreak |
|||
|
|||
#+LATEX: \pagebreak |
|||
|
|||
|
|||
* Configuration example |
|||
|
|||
|
|||
#+begin_src yaml |
|||
rsync-backup-target: |
|||
# docker-compose: |
|||
# ports: |
|||
# - "10023:22" |
|||
options: |
|||
admin: ## These keys are for the allowed rsync-backup to write stuff with rsync |
|||
myadmin: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDESdz8bWtVcDQJ68IE/KpuZM9tAq\ |
|||
ZDXGbvEVnTg16/yWqBGQg0QZdDjISsPn7D3Zr64g2qgD9n7EZghfGP9TkitvfrBYx8p\ |
|||
7JkkUyt8nxklwOlKZFD5b3PF2bHloSsmjnP8ZMp5Ar7E+tn1guGrCrTcFIebpVGR3qF\ |
|||
hRN9AlWNR+ekWo88ZlLJIrqD26jbWRJZm4nPCgqwhJwfHE3aVwfWGOqjSp4ij+jr2ac\ |
|||
Arg7eD4clBPYIqKlqbfNRD5MFAH9sbB6jkebQCAUwNRwV7pKwCEt79HnCMoMjnZh6Ww\ |
|||
6TlHIFw936C2ZiTBuofMx7yoAeqpifyzz/T5wsFLYWwSnX rsync@zen" |
|||
#+end_src |
|||
|
|||
** Adding new keys for backup |
|||
|
|||
This can be done through the admin accounts configured in =compose.yml=. |
|||
|
|||
You can use then =ssh myadmin@$RSYNC_BACKUP_TARGET ssh-key=: |
|||
|
|||
#+begin_example |
|||
$ ssh myadmin@$RSYNC_BACKUP_TARGET ssh-key ls |
|||
$ ssh myadmin@$RSYNC_BACKUP_TARGET ssh-key add "ssh-rsa AAA...Jdhwhv rsync@sourcelabel" |
|||
$ ssh myadmin@$RSYNC_BACKUP_TARGET ssh-key ls |
|||
..Jdhwhv sourcelabel |
|||
$ ssh myadmin@$RSYNC_BACKUP_TARGET ssh-key rm sourcelabel |
|||
$ ssh myadmin@$RSYNC_BACKUP_TARGET ssh-key ls |
|||
$ |
|||
#+end_example |
|||
|
|||
|
|||
* Troubleshooting |
|||
|
|||
** Faking access from client |
|||
|
|||
This should work: |
|||
|
|||
#+begin_src sh |
|||
RSYNC_BACKUP_TARGET_IP=172.18.0.2 |
|||
rsync -azvA -e "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \ |
|||
/tmp/toto "$RSYNC_BACKUP_TARGET":/var/mirror/client1 |
|||
#+end_src |
|||
|
|||
** Direct ssh access should be refused |
|||
|
|||
#+begin_src sh |
|||
RSYNC_BACKUP_TARGET_IP=172.18.0.2 |
|||
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \ |
|||
"$RSYNC_BACKUP_TARGET" |
|||
#+end_src |
|||
|
|||
** Wrong directory should be refused |
|||
|
|||
#+begin_src sh |
|||
RSYNC_BACKUP_TARGET_IP=172.18.0.2 |
|||
rsync -azvA -e "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" \ |
|||
/tmp/toto "$RSYNC_BACKUP_TARGET":/var/mirror/client2 |
|||
#+end_src |
@ -0,0 +1,79 @@ |
|||
#!/bin/bash |
|||
|
|||
## Note that the shebang is not used, but it's the login shell that |
|||
## will execute this command. |
|||
|
|||
exname=$(basename "$0") |
|||
|
|||
mkdir -p /var/log/rsync |
|||
|
|||
LOG="/var/log/rsync/$exname.log" |
|||
|
|||
|
|||
ssh_connection=(${SSH_CONNECTION}) |
|||
SSH_SOURCE_IP="${ssh_connection[0]}:${ssh_connection[1]}" |
|||
|
|||
log() { |
|||
printf "%s [%s] %s - %s\n" \ |
|||
"$(date --rfc-3339=seconds)" "$$" "$SSH_SOURCE_IP" "$*" \ |
|||
>> "$LOG" |
|||
} |
|||
|
|||
log "NEW ADMIN CONNECTION" |
|||
|
|||
reject() { |
|||
log "REJECTED: $SSH_ORIGINAL_COMMAND" |
|||
# echo "ORIG: $SSH_ORIGINAL_COMMAND" >&2 |
|||
echo "Your command has been rejected and reported to sys admin." >&2 |
|||
exit 1 |
|||
} |
|||
|
|||
|
|||
if [[ "$SSH_ORIGINAL_COMMAND" =~ [\&\(\{\;\<\>\`\$\}] ]]; then |
|||
log "BAD CHARS DETECTED" |
|||
# echo "Bad chars: $SSH_ORIGINAL_COMMAND" >&2 |
|||
reject |
|||
fi |
|||
|
|||
if [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key add ssh-rsa "[a-zA-Z0-9/+]+" "[a-zA-Z0-9._-]+"@"[a-zA-Z0-9._-]+""$ ]]; then |
|||
log "ACCEPTED: $SSH_ORIGINAL_COMMAND" |
|||
|
|||
## Interpret \ to allow passing spaces (want to avoid possible issue with \n) |
|||
#read -a ssh_args <<< "${SSH_ORIGINAL_COMMAND}" |
|||
ssh_args=(${SSH_ORIGINAL_COMMAND}) |
|||
|
|||
# echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 |
|||
exec sudo /usr/local/sbin/ssh-key "${ssh_args[@]:1}" |
|||
elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key ls"$ ]]; then |
|||
log "ACCEPTED: $SSH_ORIGINAL_COMMAND" |
|||
|
|||
## Interpret \ to allow passing spaces (want to avoid possible issue with \n) |
|||
#read -a ssh_args <<< "${SSH_ORIGINAL_COMMAND}" |
|||
ssh_args=(${SSH_ORIGINAL_COMMAND}) |
|||
|
|||
# echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 |
|||
exec /usr/local/sbin/ssh-key "${ssh_args[@]:1}" |
|||
elif [[ "$SSH_ORIGINAL_COMMAND" =~ ^"ssh-key rm "[a-zA-Z0-9._-]+$ ]]; then |
|||
log "ACCEPTED: $SSH_ORIGINAL_COMMAND" |
|||
|
|||
## Interpret \ to allow passing spaces (want to avoid possible issue with \n) |
|||
#read -a ssh_args <<< "${SSH_ORIGINAL_COMMAND}" |
|||
ssh_args=(${SSH_ORIGINAL_COMMAND}) |
|||
|
|||
# echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 |
|||
exec sudo /usr/local/sbin/ssh-key "${ssh_args[@]:1}" |
|||
else |
|||
|
|||
log "NOT MATCHING ANY ALLOWED COMMAND" |
|||
reject |
|||
fi |
|||
|
|||
## For other commands, like `find` or `md5`, that could be used to |
|||
## challenge the backups and check that archive is actually |
|||
## functional, I would suggest to write a simple command that takes no |
|||
## arguments, so as to prevent allowing wildcards or suspicious |
|||
## contents. Letting `find` go through is dangerous for instance |
|||
## because of the `-exec`. And path traversal can be done also when |
|||
## allowing /my/path/* by using '..'. This is why a fixed purpose |
|||
## embedded executable will be much simpler to handle, and to be honest |
|||
## we don't need much more. |
@ -0,0 +1,108 @@ |
|||
#!/bin/bash |
|||
|
|||
RSYNC_KEY_PATH=/etc/rsync/keys |
|||
|
|||
|
|||
ANSI_ESC=$'\e[' |
|||
|
|||
NORMAL="${ANSI_ESC}0m" |
|||
|
|||
GRAY="${ANSI_ESC}1;30m" |
|||
RED="${ANSI_ESC}1;31m" |
|||
GREEN="${ANSI_ESC}1;32m" |
|||
YELLOW="${ANSI_ESC}1;33m" |
|||
BLUE="${ANSI_ESC}1;34m" |
|||
PINK="${ANSI_ESC}1;35m" |
|||
CYAN="${ANSI_ESC}1;36m" |
|||
WHITE="${ANSI_ESC}1;37m" |
|||
|
|||
DARKGRAY="${ANSI_ESC}0;30m" |
|||
DARKRED="${ANSI_ESC}0;31m" |
|||
DARKGREEN="${ANSI_ESC}0;32m" |
|||
DARKYELLOW="${ANSI_ESC}0;33m" |
|||
DARKBLUE="${ANSI_ESC}0;34m" |
|||
DARKPINK="${ANSI_ESC}0;35m" |
|||
DARKCYAN="${ANSI_ESC}0;36m" |
|||
DARKWHITE="${ANSI_ESC}0;37m" |
|||
|
|||
|
|||
ssh-key-ls() { |
|||
local f content |
|||
for f in "${RSYNC_KEY_PATH}"/backup/*.pub; do |
|||
[ -e "$f" ] || continue |
|||
ident=${f##*/} |
|||
ident=${ident%.pub} |
|||
content=$(cat "$f") |
|||
key=${content#* } |
|||
key=${key% *} |
|||
printf "${DARKGRAY}..${NORMAL}%24s ${DARKCYAN}%s${NORMAL}\n" "${key: -24}" "$ident" |
|||
done |
|||
} |
|||
|
|||
|
|||
ssh-key-rm() { |
|||
local ident="$1" delete |
|||
|
|||
delete="${RSYNC_KEY_PATH}/backup/$ident.pub" |
|||
if ! [ -e "$delete" ]; then |
|||
echo "Error: key '$ident' not found." >&2 |
|||
return 1 |
|||
fi |
|||
rm "$delete" |
|||
|
|||
/usr/local/sbin/ssh-update-keys |
|||
} |
|||
|
|||
|
|||
ssh-key-add() { |
|||
local type="$1" key="$2" email="$3" |
|||
|
|||
[ "$1" == "ssh-rsa" ] || { |
|||
echo "Error: expecting ssh-rsa key type" >&2 |
|||
return 1 |
|||
} |
|||
|
|||
## 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" |
|||
content="$type $key $email" |
|||
ident="${email##*@}" |
|||
target="${RSYNC_KEY_PATH}/backup/$ident.pub" |
|||
if [ -e "$target" ]; then |
|||
old_content=$(cat "$target") |
|||
if [ "$content" == "$old_content" ]; then |
|||
echo "Provided key already present for '$ident'." >&2 |
|||
return 0 |
|||
fi |
|||
echo "Replacing key for '$ident'." >&2 |
|||
fi |
|||
echo "$content" > "$target" |
|||
|
|||
/usr/local/sbin/ssh-update-keys |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
case "$1" in |
|||
"add") |
|||
shift |
|||
ssh-key-add "$@" |
|||
;; |
|||
"rm") |
|||
shift |
|||
ssh-key-rm "$@" |
|||
;; |
|||
"ls") |
|||
shift |
|||
ssh-key-ls "$@" |
|||
;; |
|||
*) |
|||
echo "Unknown command '$1'." |
|||
;; |
|||
esac |
|||
|
|||
|
|||
|
|||
|
@ -0,0 +1,41 @@ |
|||
#!/bin/bash |
|||
|
|||
|
|||
## |
|||
## code |
|||
## |
|||
|
|||
KEYS=/etc/rsync/keys |
|||
RSYNC_HOME=/var/lib/rsync |
|||
|
|||
mkdir -p "$RSYNC_HOME/.ssh" |
|||
|
|||
## |
|||
## New |
|||
## |
|||
|
|||
touch "$RSYNC_HOME"/.ssh/authorized_keys.new |
|||
|
|||
for f in "$KEYS"/backup/*.pub; do |
|||
[ -e "$f" ] || continue |
|||
content=$(cat "$f") |
|||
ident="${f##*/}" |
|||
ident="${ident%.pub}" |
|||
if ! [[ "$ident" =~ ^[a-zA-Z0-9._-]+$ ]]; then |
|||
echo "bad: '$ident'" >&2 |
|||
continue |
|||
fi |
|||
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} |
|||
mv "$RSYNC_HOME"/.ssh/authorized_keys{.new,} |
|||
|
|||
chown rsync:rsync -R "$RSYNC_HOME"/.ssh -R |
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue