#!/bin/bash trap nfcunmount EXIT set -o noglob SCRIPT="$(readlink -f "${BASH_SOURCE}")" CONFIGURATION="/home/user/scripts/admin.conf" if [ "$EUID" -ne 0 ]; then echo "Ce script nécessite des privilèges administrateur." echo "Relance avec sudo..." exec sudo "$0" "$@" fi echo "Le script est bien lancé avec sudo/root !" function show() { NC='\033[0m' if [ -z "$2" ]; then COLOR=$NC elif [ "$2" == "ERR" ]; then COLOR='\033[1;31m' else COLOR='\033[0;32m' fi printf "${COLOR}$1${NC}\n" } function assign() { KEYS=$(cat "$1"|sed 's/.*%\([A-Z0-9]*\)%.*/%\1/g'|grep "%"|tr -d '%') while read -r KEY; do REALKEY=$(eval echo \$${KEY}) show "Apply key $KEY" 1 sed -i "s#%${KEY}%#${REALKEY}#g" /mnt/usb/rclone/rclone.conf done < <(echo "${KEYS}") } function extract() { ALLCONTENT=$(cat "${SCRIPT}"|grep "#$1"|sed "s/^#$1\(.*\)$/\1/") CONTENT=$(echo "${ALLCONTENT}"|tail -n +2) CONTENTSIZE=$(echo "${CONTENT}"|wc -c) FILEINFO=$(echo "${ALLCONTENT}"|head -n1) FILENAME=$(echo "${FILEINFO}"|cut -d"," -f1) FILEMOD=$(echo "${FILEINFO}"|cut -d"," -f2) show "Extract piece $1 (${CONTENTSIZE}o) at ${FILENAME} with ${FILEMOD}" echo "${CONTENT}" > "${FILENAME}" chmod ${FILEMOD} "${FILENAME}" } function excludepath() { EXCLUD="" while read SRC; do if [ ! -z "${SRC}" ]; then EXCLUD="${EXCLUD} --exclude ${SRC}" fi done < <(echo "$1"|tr "," "\n") echo "${EXCLUD}" } function nfcunmount() { TMPFS=$(mount -t tmpfs|grep "/mnt/usb") if [ ! -z "${TMPFS}" ]; then umount /mnt/usb fi TMPFS=$(mount -t tmpfs|grep "/mnt/usb") if [ ! -z "${TMPFS}" ]; then show "Unable to umount" ERR fi } function nfcmountonly() { if [ ! -d /mnt/usb ]; then mkdir -p /mnt/usb fi TMPFS=$(mount -t tmpfs|grep "/mnt/usb") if [ -z "${TMPFS}" ]; then mount -t tmpfs tmpfs /mnt/usb fi } function rclonemount() { if [ ! -f /mnt/usb/rclone/rclone.conf ]; then show "NO RCLONE CONFIGURATION FILE" ERR nfcmountonly if [ ! -d /mnt/usb/rclone ]; then show "Create /mnt/usb/rclone" 1 mkdir -p /mnt/usb/rclone show "Create rclone.conf" 1 extract 5 TOKEN=$(${ZFS} get rclone:token $1 -o value -H|decrypt_now "{\"url\": \"${URL}\"}" 2> /dev/null) if [[ "${TOKEN}" == "" ]]; then show "Unable to find connexion information: token" nfcunmount exit fi if [[ "$2" == "rclone" ]]; then show "RCLONE mode" PASSWD1=$(${ZFS} get rclone:password1 $1 -o value -H|decrypt_now "{\"url\": \"${URL}\"}" 2> /dev/null) if [[ "${PASSWD1}" == "" ]]; then show "Unable to find connexion information: passwd1" nfcunmount exit fi PASSWD2=$(${ZFS} get rclone:password2 $1 -o value -H|decrypt_now "{\"url\": \"${URL}\"}" 2> /dev/null) if [[ "${PASSWD2}" == "" ]]; then show "Unable to find connexion information: passwd2" nfcunmount exit fi REMOTE=$(${ZFS} get sync:dstpath $1 -o value -H) if [[ "${REMOTE}" == "" ]]; then show "Unable to find connexion information: dstpath" nfcunmount exit fi ACTIVE=$(${ZFS} get sync:active $1 -o value -H) if [[ ! "${ACTIVE}" == "on" ]]; then show "Sync not active" nfcunmount exit fi RCLONE_EXCLUDE=$(${ZFS} get sync:exclude $1 -o value -H) RCLONE_VOLS="/$1" else PASSWD1=$(${ZFS} get restic:password1 $1 -o value -H|decrypt_now "{\"url\": \"${URL}\"}" 2> /dev/null) if [[ "${PASSWD1}" == "" ]]; then show "Unable to find connexion information: passwd1" nfcunmount exit fi PASSWD2=$(${ZFS} get restic:password2 $1 -o value -H|decrypt_now "{\"url\": \"${URL}\"}" 2> /dev/null) if [[ "${PASSWD2}" == "" ]]; then show "Unable to find connexion information: passwd2" nfcunmount exit fi show "RESTIC mode" REMOTE=$(${ZFS} get backup:dstpath $1 -o value -H) if [[ "${REMOTE}" == "" ]]; then show "Unable to find connexion information: dstpath" nfcunmount exit fi ACTIVE=$(${ZFS} get backup:active $1 -o value -H) if [[ ! "${ACTIVE}" == "on" ]]; then show "Backup not active" nfcunmount exit fi RESTIC_PASSWORD=$(${ZFS} get restic:password $1 -o value -H|decrypt_now "{\"url\": \"${URL}\"}" 2> /dev/null) if [[ "${RESTIC_PASSWORD}" == "" ]]; then show "Unable to find connexion information: passwd" nfcunmount exit fi RESTIC_EXCLUDE=$(${ZFS} get backup:exclude $1 -o value -H) RESTIC_ALL=$(${ZFS} get backup:retention $1 -o value -H) RESTIC_MONTHLY=$(echo "${RESTIC_ALL}"|cut -d',' -f1) RESTIC_WEEKLY=$(echo "${RESTIC_ALL}"|cut -d',' -f2) RESTIC_DAILY=$(echo "${RESTIC_ALL}"|cut -d',' -f3) RESTIC_VOLS="/$1" fi assign /mnt/usb/rclone/rclone.conf fi fi TOKEN="" PASSWD1="" PASSWD2="" show "List remotes" 1 ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf listremotes show "Connect to Cloud without cyphering" 1 ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf lsd pcloud:/ show "Connect to Cloud with cyphering" 1 ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf lsd crypt:/ } function attribute() { VAL="" ATTRIBUTE=$(${ZFS} get $1 $4 -o value -H) if [[ "${ATTRIBUTE}" == "-" ]]; then ATTRIBUTE="$5" show "Init attribute $1 with $5" ${ZFS} set $1=$5 $4 fi if [[ "$2" == "passwd" ]]; then ATTRIBUTE_DEC=$(echo -en "${ATTRIBUTE}"|decrypt_now "{\"url\": \"${URL}\"}" 2> /dev/null) BEGIN=$(echo "${ATTRIBUTE_DEC}"|head -c 10) MIDDLE="..." END=$(echo "${ATTRIBUTE_DEC}"|tail -c 10) BLOB="${BEGIN}${MIDDLE}${END}" read -p "$3 [${BLOB}]:" ASK_DEC /dev/null) fi ATTRIBUTE_DEC="" ASK_DEC="" elif [[ "$2" == "bool" ]]; then if [[ "${ATTRIBUTE}" == "on" ]]; then VALUE="off" else VALUE="on" fi read -p "$3 [${ATTRIBUTE}]/${VALUE}:" ASK &2 echo echo "Usage: clevis encrypt tang CONFIG [-y] < PLAINTEXT > JWE" echo echo "$SUMMARY" echo echo " -y Use this option for skipping the advertisement" echo " trust check. This can be useful in automated" echo " deployments" echo echo "This command uses the following configuration properties:" echo echo " url: The base URL of the Tang server (REQUIRED)" echo echo " thp: The thumbprint of a trusted signing key" echo echo " adv: A filename containing a trusted advertisement" echo " adv: A trusted advertisement (raw JSON)" echo echo "Obtaining the thumbprint of a trusted signing key is easy. If you" echo "have access to the Tang server's database directory, simply do:" echo echo " $ jose jwk thp -i \$DBDIR/\$SIG.jwk " echo echo "Alternatively, if you have certainty that your network connection" echo "is not compromised (not likely), you can download the advertisement" echo "yourself using:" echo echo " $ curl -f \$URL/adv > adv.jws" echo exit 2 fi if ! cfg="$(jose fmt -j- -Oo- <<< "$1" 2>/dev/null)"; then echo "Configuration is malformed!" >&2 exit 1 fi CLEVIS_DEFAULT_THP_ALG=S256 # SHA-256. CLEVIS_ALTERNATIVE_THP_ALGS=S1 # SHA-1. trust= [ -n "${2}" ] && [ "${2}" == "-y" ] && trust=yes if ! url="$(jose fmt -j- -Og url -u- <<< "$cfg")"; then echo "Missing the required 'url' property!" >&2 exit 1 fi thp="$(jose fmt -j- -Og thp -Su- <<< "$cfg")" || true ### Get the advertisement if jws="$(jose fmt -j- -g adv -Oo- <<< "$cfg")"; then thp="${thp:-any}" elif jws="$(jose fmt -j- -g adv -Su- <<< "$cfg")"; then if ! [ -f "$jws" ]; then echo "Advertisement file '$jws' not found!" >&2 exit 1 fi if ! jws="$(jose fmt --json="${jws}" -Oo- 2>/dev/null)"; then echo "Advertisement file '$jws' is malformed!" >&2 exit 1 fi thp="${thp:-any}" elif ! jws="$(curl -sfg "$url/adv/$thp")"; then echo "Unable to fetch advertisement: '$url/adv/$thp'!" >&2 exit 1 fi if ! jwks="$(jose fmt --json="${jws}" -Og payload -SyOg keys \ -AUo- 2>/dev/null)"; then echo "Advertisement is malformed!" >&2 exit 1 fi ### Check advertisement validity ver="$(jose jwk use -i- -r -u verify -o- <<< "$jwks")" if ! jose jws ver -i "$jws" -k- -a <<< "$ver"; then echo "Advertisement is missing signatures!" >&2 exit 1 fi ### Check advertisement trust if [ -z "${trust}" ]; then if [ -z "$thp" ]; then echo "The advertisement contains the following signing keys:" >&2 echo >&2 jose jwk thp -i- -a "${CLEVIS_DEFAULT_THP_ALG}" <<< "$ver" >&2 echo >&2 elif [ "$thp" != "any" ] && \ ! jose jwk thp -i- -f "${thp}" -a "${CLEVIS_DEFAULT_THP_ALG}" \ -o /dev/null <<< "$ver"; then # Thumbprint of trusted JWK did not match the signature. Let's check # alternative thumbprints generated with clevis supported hash # algorithms to be sure. for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do srv="$(jose jwk thp -i- -f "${thp}" -a "${alg}" <<< "${ver}")" \ && break done if [ -z "${srv}" ]; then echo "Trusted JWK '$thp' did not sign the advertisement!" >&2 exit 1 fi fi fi ### Perform encryption if ! enc="$(jose jwk use -i- -r -u deriveKey -o- <<< "$jwks")"; then echo "Key derivation key not available!" >&2 exit 1 fi jose fmt -j "$enc" -Og keys -A || enc="{\"keys\":[$enc]}" if ! jwk="$(jose fmt -j- -Og keys -Af- <<< "$enc")"; then echo "No exchange keys found!" >&2 exit 1 fi jwk="$(jose fmt -j- -Od key_ops -o- <<< "$jwk")" jwk="$(jose fmt -j- -Od alg -o- <<< "$jwk")" kid="$(jose jwk thp -i- -a "${CLEVIS_DEFAULT_THP_ALG}" <<< "$jwk")" jwe='{"protected":{"alg":"ECDH-ES","enc":"A256GCM","clevis":{"pin":"tang","tang":{}}}}' jwe="$(jose fmt -j "$jwe" -g protected -q "$kid" -s kid -UUo-)" jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -q "$url" -s url -UUUUo-)" jwe="$(jose fmt -j "$jwe" -g protected -g clevis -g tang -j- -s adv -UUUUo- <<< "$jwks")" exec jose jwe enc -i- -k- -I- -c < <(echo -n "$jwe$jwk"; /bin/cat) echo -en "" } function decrypt_now() { [ $# -eq 1 ] && [ "$1" == "--summary" ] && exit 2 if [ -t 0 ]; then exec >&2 echo echo "Usage: clevis decrypt tang < JWE > PLAINTEXT" echo exit 2 fi read -r -d . hdr if ! jhd="$(jose b64 dec -i- <<< "$hdr")"; then echo "Error decoding JWE protected header!" >&2 exit 1 fi if [ "$(jose fmt -j- -Og clevis -g pin -u- <<< "$jhd")" != "tang" ]; then echo "JWE pin mismatch!" >&2 exit 1 fi if ! clt="$(jose fmt -j- -Og epk -Oo- <<< "$jhd")"; then echo "JWE missing required 'epk' header parameter!" >&2 exit 1 fi if ! kid="$(jose fmt -j- -Og kid -Su- <<< "$jhd")"; then echo "JWE missing required 'kid' header parameter!" >&2 exit 1 fi # Tang advertisement validation. if ! keys="$(jose fmt -j- -Og clevis -g tang -g adv -Oo- <<< "${jhd}")"; then echo "JWE missing required 'clevis.tang.adv' header parameter!" >&2 exit 1 fi # Check if the thumbprint we have in `kid' is in the advertised keys. CLEVIS_DEFAULT_THP_ALG=S256 # SHA-256. CLEVIS_DEFAULT_THP_LEN=43 # Length of SHA-256 thumbprint. CLEVIS_ALTERNATIVE_THP_ALGS=S1 # SHA-1. # Issue a warning if we are using a hash that has a shorter length than the # default one. if [ "${#kid}" -lt "${CLEVIS_DEFAULT_THP_LEN}" ]; then echo "WARNING: tang using a deprecated hash for the JWK thumbprints" >&2 fi if ! srv="$(jose jwk thp -i- -f "${kid}" -a "${CLEVIS_DEFAULT_THP_ALG}" \ <<< "${keys}")"; then # `kid' thumprint not in the advertised keys, but it's possible it was # generated using a different algorithm than the default one. # Let us try the alternative supported algorithms to make sure `kid' # really is not part of the advertised keys. for alg in ${CLEVIS_ALTERNATIVE_THP_ALGS}; do srv="$(jose jwk thp -i- -f "$kid" -a "${alg}" <<< "${keys}")" && break done if [ -z "${srv}" ]; then echo "JWE header validation of 'clevis.tang.adv' failed: key thumbprint does not match" >&2 exit 1 fi fi if ! url="$(jose fmt -j- -Og clevis -g tang -g url -Su- <<< "$jhd")"; then echo "JWE missing required 'clevis.tang.url' header parameter!" >&2 exit 1 fi if ! crv="$(jose fmt -j- -Og crv -Su- <<< "$clt")"; then echo "Unable to determine EPK's curve!" >&2 exit 1 fi if ! eph="$(jose jwk gen -i "{\"alg\":\"ECMR\",\"crv\":\"$crv\"}")"; then echo "Error generating ephemeral key!" >&2 exit 1 fi xfr="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$clt$eph")" rec_url="$url/rec/$kid" ct="Content-Type: application/jwk+json" if ! rep="$(curl -sfg -X POST -H "$ct" --data-binary @- "$rec_url" <<< "$xfr")"; then echo "Error communicating with server $url" >&2 exit 1 fi if ! rep="$(jose fmt -j- -Og kty -q EC -EUUg crv -q "$crv" -EUUo- <<< "$rep")"; then echo "Received invalid server reply!" >&2 exit 1 fi tmp="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$eph$srv")" rep="$(jose jwk pub -i- <<< "$rep")" jwk="$(jose jwk exc -l- -r- <<< "$rep$tmp")" (echo -n "$jwk$hdr."; /bin/cat) | exec jose jwe dec -k- -i- echo -en "" } function rcloneapp() { if [ -z "$1" ]; then help_rclone fi case "$2" in copy) DEST="/tmp/" show "Copy from $3 to ${DEST}" 1 rclonemount $1 rclone ${RCLONE_CMD} -vv --config /mnt/usb/rclone/rclone.conf copy crypt:/$2 ${DEST} ;; dryrun) show "Dry run..." 1 echo "********* Synchonize *************" >> /var/log/rclone.log date >> /var/log/rclone.log rclonemount $1 rclone excluded=$(excludepath $RCLONE_EXCLUDE) while read src; do dstemp=$(basename "${src^}") dst=${dstemp^} show "$src => $dst" echo "$src => $dst" >> /var/log/rclone.log ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf sync -c -P --dry-run --links --checkers=20 --low-level-retries=10 --retries=10 --retries-sleep=1s --transfers=20 --delete-excluded $excluded --exclude ".zfs/**" $src crypt:/${dst}/ 2>&1 | tee -a /var/log/rclone.log done < <(echo "$RCLONE_VOLS"|tr "," "\n") echo "***********************************" >> /var/log/rclone.log ;; now) show "Synchronize..." 1 echo "********* Synchonize *************" >> /var/log/rclone.log date >> /var/log/rclone.log rclonemount $1 rclone excluded=$(excludepath $RCLONE_EXCLUDE) while read src; do dstemp=$(basename "${src^}") dst=${dstemp^} show "$src => $dst" echo "$src => $dst" >> /var/log/rclone.log ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf sync -c -P ${DRY} --links --checkers=20 --low-level-retries=10 --retries=10 --retries-sleep=1s --transfers=20 --delete-excluded $excluded --exclude ".zfs/**" $src crypt:/${dst}/ 2>&1 | tee -a /var/log/rclone.log done < <(echo "$RCLONE_VOLS"|tr "," "\n") echo "***********************************" >> /var/log/rclone.log ;; view) show "View..." 1 rclonemount $1 rclone ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf ncdu crypt: ;; ls) show "View files..." 1 rclonemount $1 rclone ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf lsd crypt: -R ;; check) show "Check..." 1 echo "************** Check **************" >> /var/log/rclone.log date >> /var/log/rclone.log rclonemount $1 rclone excluded=$(excludepath $RCLONE_EXCLUDE) while read src; do dstemp=$(basename "${src^}") dst=${dstemp^} show "$src => $dst" echo "$src => $dst" >> /var/log/rclone.log ${RCLONE_CMD} --config /mnt/usb/rclone/rclone.conf check -c -P --differ /var/log/differ_${dst}.log --links --checkers=20 --low-level-retries=10 --retries=10 --retries-sleep=1s --transfers=20 $excluded --exclude ".zfs/**" $src crypt:/${dst}/ 2>&1 | tee -a /var/log/rclone.log done < <(echo "$RCLONE_VOLS"|tr "," "\n") echo "***********************************" >> /var/log/rclone.log ;; *) help_rclone ;; esac nfcunmount } function resticapp() { if [ -z "$1" ]; then help_restic fi export RCLONE_CONFIG export RESTIC_PASSWORD case "$2" in init) show "Initializing..." 1 echo "********* Initialiazing *************" >> /var/log/restic.log date >> /var/log/restc.log rclonemount $1 restic ${RESTIC_CMD} init -r rclone:crypt:/ | tee -a /var/log/restic.log echo "***********************************" >> /var/log/restic.log ;; unlock) show "Unlocking..." 1 echo "********* Unlocking *************" >> /var/log/restic.log date >> /var/log/restc.log rclonemount $1 restic ${RESTIC_CMD} unlock -r rclone:crypt:/ | tee -a /var/log/restic.log echo "***********************************" >> /var/log/restic.log ;; ls) show "View files..." 1 rclonemount $1 restic ${RESTIC_CMD} ls -r rclone:crypt:/ $3 ;; view) show "View..." 1 rclonemount $1 restic ${RESTIC_CMD} snapshots -r rclone:crypt:/ ;; check) show "Check..." 1 echo "************** Check **************" >> /var/log/restic.log date >> /var/log/restic.log rclonemount $1 restic ${RESTIC_CMD} check --read-data -r rclone:crypt:/ | tee -a /var/log/restic.log ${RESTIC_CMD} rebuild-index -r rclone:crypt:/ | tee -a /var/log/restic.log echo "***********************************" >> /var/log/restic.log ;; prune) show "Cleaning..." 1 echo "************** Cleaning **************" >> /var/log/restic.log date >> /var/log/restic.log rclonemount $1 restic ${RESTIC_CMD} prune -r rclone:crypt:/ | tee -a /var/log/restic.log echo "***********************************" >> /var/log/restic.log ;; remove) show "Removing..." 1 echo "************** Removing **************" >> /var/log/restic.log date >> /var/log/restic.log rclonemount $1 restic ${RESTIC_CMD} forget $2 -r rclone:crypt:/ | tee -a /var/log/restic.log echo "***********************************" >> /var/log/restic.log ;; now) show "Backing up..." 1 echo "************** Backup **************" >> /var/log/restic.log date >> /var/log/restic.log rclonemount $1 restic excluded=$(excludepath $RESTIC_EXCLUDE) while read src; do dstemp=$(basename "${src^}") dst=${dstemp^} show "$src => $dst" echo "$src => $dst" >> /var/log/restic.log ${RESTIC_CMD} backup $src -r rclone:crypt:/ --verbose=1 $excluded --exclude ".zfs/**" --tag admin_command|tee -a /var/log/restic.log ${RESTIC_CMD} forget -r rclone:crypt:/ --tag admin_command --keep-monthly=$RESTIC_MONTHLY --keep-weekly=$RESTIC_WEEKLY --keep-daily=$RESTIC_DAILY done < <(echo "$RESTIC_VOLS"|tr "," "\n") echo "***********************************" >> /var/log/restic.log ;; *) help_restic ;; esac nfcunmount } if [[ ! -f "${CONFIGURATION}" ]]; then show "Creating configuration file..." extract 4 fi source "${CONFIGURATION}" nfcunmount COMMAND="$1" shift case "${COMMAND}" in config) configure ;; backup) resticapp $1 $2 $3 ;; sync) rcloneapp $1 $2 $3 ;; nfcunmount) nfcunmount ;; tangcrypt) cat /dev/stdin|encrypt_now "{\"url\": \"${URL}\"}" ;; tangdecrypt) cat /dev/stdin|decrypt_now "{\"url\": \"${URL}\"}" ;; *) help ;; esac #4/home/user/scripts/admin.conf,644 #4# Hard settings #4ALIB="--module /usr/lib/x86_64-linux-gnu/libykcs11.so.2" #4ZFS="zfs" #4PZFS="zfs" #4ZPOOL="zpool" #4PZPOOL="zpool" #4PATH="/usr/local/bin:/usr/bin:$PATH" #4FONT="" #4MAXSIZE=700 #4RCLONE_CMD="rclone" #4RESTIC_CMD="restic" #4 #4# Settings #4RCLONE_CONFIG="/mnt/usb/rclone/rclone.conf" #4URL="https://tang.ia86.cc" #4 #4# Default parameters #4RCLONE_PATH="/hetzner/kube.ia86.cc" #4RCLONE_EXCLUDE="" #4RESTIC_PATH="/hetzner/restic" #4RESTIC_MONTHLY="12" #4RESTIC_WEEKLY="10" #4RESTIC_DAILY="31" #4RESTIC_EXCLUDE="" #5/mnt/usb/rclone/rclone.conf,644 #5[pcloud] #5type = pcloud #5hostname = eapi.pcloud.com #5token = {"access_token":"%TOKEN%","token_type":"bearer","expiry":"0001-01-01T00:00:00Z"} #5[crypt] #5type = crypt #5remote = pcloud:%REMOTE% #5filename_encryption = standard #5directory_name_encryption = true #5password = %PASSWD1% #5password2 = %PASSWD2% #5[sauvegarde] #5type = crypt #5remote = pcloud:%REMOTE% #5filename_encryption = standard #5directory_name_encryption = true #5password = %PASSWD3% #5password2 = %PASSWD4%