diff --git a/rsync-backup-target/build/Dockerfile b/rsync-backup-target/build/Dockerfile index e5d2e39..554f34b 100644 --- a/rsync-backup-target/build/Dockerfile +++ b/rsync-backup-target/build/Dockerfile @@ -1,24 +1,24 @@ -FROM debian:jessie +FROM alpine:3.9 MAINTAINER Valentin Lab -RUN apt-get update && \ - DEBIAN_FRONTEND=noninteractive apt-get install --force-yes -y --no-install-recommends openssh-server sudo rsync && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* - - -COPY ./src/usr/local/sbin/* /usr/local/sbin/ +RUN apk add rsync sudo bash openssh-server +RUN ssh-keygen -A ## New user/group rsync/rsync with home dir in /var/lib/rsync RUN mkdir -p /var/lib/rsync && \ - groupadd -r rsync && \ - useradd -r rsync -d /var/lib/rsync -g rsync && \ + addgroup -S rsync && \ + adduser -S rsync -h /var/lib/rsync -G rsync && \ chown rsync:rsync /var/lib/rsync +## Without this, account is concidered locked by SSH +RUN sed -ri 's/^rsync:!:/rsync:*NP*:/g' /etc/shadow + +## Withouth this, force-command will not run +RUN sed -ri 's%^(rsync.*:)[^:]+$%\1/bin/bash%g' /etc/passwd ## Allow rsync to access /var/mirror -COPY /src/etc/sudoers.d/rsync /etc/sudoers.d/rsync +COPY /src / RUN chmod 440 /etc/sudoers.d/* diff --git a/rsync-backup-target/build/entrypoint.sh b/rsync-backup-target/build/entrypoint.sh index 11583bc..306f69a 100755 --- a/rsync-backup-target/build/entrypoint.sh +++ b/rsync-backup-target/build/entrypoint.sh @@ -7,15 +7,21 @@ chmod 440 /etc/sudoers.d/* -R -KEYS=/etc/rsync/keys/ +KEYS=/etc/rsync/keys RSYNC_HOME=/var/lib/rsync mkdir -p "$RSYNC_HOME/.ssh" -for f in "$KEYS"/*; do +for f in "$KEYS"/*.pub; do [ -e "$f" ] || continue content=$(cat "$f") - echo "command=\"/usr/local/sbin/ssh-cmd-validate\" $content" + ident="${f##*/}" + ident="${ident%.pub}" + if ! [[ "$ident" =~ ^[a-zA-Z0-9._-]+$ ]]; then + echo "bad: '$ident'" + 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 chown rsync:rsync -R "$RSYNC_HOME"/.ssh -R diff --git a/rsync-backup-target/build/src/etc/ssh/sshd_config b/rsync-backup-target/build/src/etc/ssh/sshd_config new file mode 100644 index 0000000..6cfe4a7 --- /dev/null +++ b/rsync-backup-target/build/src/etc/ssh/sshd_config @@ -0,0 +1,118 @@ +# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/bin:/usr/bin:/sbin:/usr/sbin + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +#PermitRootLogin prohibit-password +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#PubkeyAuthentication yes + +# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 +# but this is overridden so installations will only check .ssh/authorized_keys +AuthorizedKeysFile .ssh/authorized_keys + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +PasswordAuthentication no +PermitEmptyPasswords no + +# Change to no to disable s/key passwords +ChallengeResponseAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +#UsePAM yes + +#AllowAgentForwarding yes +# Feel free to re-enable these if your use case requires them. +AllowTcpForwarding no +GatewayPorts no +X11Forwarding no +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /run/sshd.pid +#MaxStartups 10:30:100 +PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# override default of no subsystems +#Subsystem sftp /usr/lib/ssh/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server + diff --git a/rsync-backup-target/build/src/etc/sudoers.d/rsync b/rsync-backup-target/build/src/etc/sudoers.d/rsync index 7f50847..2b8b4ee 100644 --- a/rsync-backup-target/build/src/etc/sudoers.d/rsync +++ b/rsync-backup-target/build/src/etc/sudoers.d/rsync @@ -1,21 +1,4 @@ -## allow rsync to access /var/mirror - -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtprRz --delete . /var/mirror/* - -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtprRze.iLs --delete . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtprRze.iLsf --delete . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtprRze.iLsf --bwlimit=200 --delete . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtpArRze.iLsf --delete . /var/mirror/* - -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtpArRze.iLs --delete --partial-dir .rsync-partial --numeric-ids . /var/mirror/* - -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtprRze.iLs --delete --partial-dir .rsync-partial --numeric-ids . /var/mirror/* - -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlHogDtpArRze.iLs --delete --partial-dir .rsync-partial --numeric-ids . /var/mirror/* - -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtpArRze.iLsf --delete --partial-dir .rsync-partial --numeric-ids . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtpArRze.iLsf --bwlimit=200 --delete . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtpArRze.iLsfx --delete --partial-dir .rsync-partial --numeric-ids . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlogDtpArze.iLsfx --delete --partial-dir .rsync-partial --numeric-ids . /var/mirror/* -rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server -vlHogDtpArRze.iLsfx --delete --partial-dir .rsync-partial --numeric-ids . /var/mirror/* +## allow rsync to access /var/mirror, this is really not sufficient, but +## the real check is done on the ``ssh-cmd-validate`` side. +rsync ALL=(root) NOPASSWD: /usr/bin/rsync --server * . /var/mirror/* diff --git a/rsync-backup-target/build/src/usr/local/sbin/ssh-cmd-validate b/rsync-backup-target/build/src/usr/local/sbin/ssh-cmd-validate index 50f91e9..e1940b2 100755 --- a/rsync-backup-target/build/src/usr/local/sbin/ssh-cmd-validate +++ b/rsync-backup-target/build/src/usr/local/sbin/ssh-cmd-validate @@ -1,22 +1,43 @@ -#!/bin/sh +#!/bin/bash + +## Note that the shebang is not used, but it's the login shell that +## will execute this command. exname=$(basename "$0") +if [ -z "$1" ] || ! [[ "$1" =~ ^[a-zA-Z0-9._-]+$ ]]; then + logger -t "$exname" "INVALID SETUP, ARG IS: '$1'" + echo "Your command has been rejected. Contact administrator." + exit 1 +fi + reject() { logger -t "$exname" "REJECTED: $SSH_ORIGINAL_COMMAND" - echo "Your command has been rejected and reported to sys admin." + # echo "ORIG: $SSH_ORIGINAL_COMMAND" >&2 + echo "Your command has been rejected and reported to sys admin." >&2 + exit 1 } -case "$SSH_ORIGINAL_COMMAND" in - *\&* | *\(* | *\{* | *\;* | *\<* | *\`*) - reject - ;; - md5sum\ /var/mirror/*|find\ /var/mirror/*|rsync\ --server*) - echo "ACCEPTED: $SSH_ORIGINAL_COMMAND" >/tmp/accepted - logger -t "$exname" "ACCEPTED: $SSH_ORIGINAL_COMMAND" - sudo $SSH_ORIGINAL_COMMAND - ;; - *) - reject - ;; -esac + +if [[ "$SSH_ORIGINAL_COMMAND" =~ [\&\(\{\;\<\>\`\$\}] ]]; then + # echo "Bad chars: $SSH_ORIGINAL_COMMAND" >&2 + reject +fi + +if [[ "$SSH_ORIGINAL_COMMAND" =~ ^"rsync --server -"[vloHgDtpArRzCeiLsfx\.]+(" --"[a-z-]+|" --partial-dir .rsync-partial")*" . /var/mirror/$1"$ ]]; then + logger -t "$exname" "ACCEPTED: $SSH_ORIGINAL_COMMAND" + # echo "Would accept: $SSH_ORIGINAL_COMMAND" >&2 + exec sudo $SSH_ORIGINAL_COMMAND +else + 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. \ No newline at end of file diff --git a/rsync-backup-target/hooks/init b/rsync-backup-target/hooks/init index 2068833..77e111f 100755 --- a/rsync-backup-target/hooks/init +++ b/rsync-backup-target/hooks/init @@ -15,20 +15,31 @@ set -e service_def=$(get_compose_service_def "$SERVICE_NAME") -keys=$(echo "$service_def" | shyaml get-value options.keys 2>/dev/null) || true +keys=$(echo "$service_def" | shyaml -y get-value options.keys 2>/dev/null) || { + err "You must specify a ${WHITE}keys${NORMAL} struct to use this service" + exit 1 +} -[ "$keys" ] || exit 0 +[ "$(echo "$keys" | shyaml -y get-type 2>/dev/null)" == "struct" ] || { + err "Invalid value type for ${WHITE}keys${NORMAL}, please provide a struct" + exit 1 +} local_path_key=/etc/rsync/keys host_path_key="$SERVICE_CONFIGSTORE${local_path_key}" key_nb=0 volume_keys=() -while read-0 key; do - debug "Creating access key ${key_nb}" || true - echo "$key" | file_put "$host_path_key/key_${key_nb}.pub" - ((key_nb++)) || true -done < <(echo "$keys" | shyaml get-values-0) +all_keys=" " +while read-0 ident key; do + 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/${ident}.pub" +done < <(echo "$keys" | shyaml key-values-0) volume_keys+=("$host_path_key:$local_path_key:ro")