Merge pull request 'Code cleanup' (#33) from jlecour-refactoring into master

This commit is contained in:
Jérémy Lecour 2020-04-17 15:32:38 +02:00
commit 8e2ed4eae7
36 changed files with 1575 additions and 563 deletions

38
CHANGELOG.md Normal file
View file

@ -0,0 +1,38 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
* Shellcheck directives to have 0 warnings and errors
* Ability to override critical/warning thresholds per jail for bkctld-check
* Support new location for jail configuration (/etc/evobackup/<jail_name>.d/)
* Lock per jail and inc when creating incs
* Global lock when removing incs (kill the currently running instance)
* Create a blank SSH "authorized_keys" file on jail init
* Many new tests with BATS
* Check for firewall configuration in bkcld-check
* Run the test suite on Buster (ext4/btrfs) in addition of Stretch (ext4/btrfs)
### Changed
* Extract variables and heper functions to reduce repetition of knowledge
* Consistent naming of variables in scripts and functions
* Consistent log messages between functions ad commands
* Raise errors if required function arguments are missing
* Configure locales in Vagrant VM
* Split BATS tests file and use helper functions
* Improve "lib" detection
* Revamp the README
### Deprecated
### Removed
### Fixed
### Security

View file

@ -1,38 +1,44 @@
Bkctld (aka evobackup)
Bkctld (aka server-side evobackup)
=========
Bkctld is a shell script that creates and manages a backup server
which can handle the backups of many other servers (clients). It
is licensed under the AGPLv3.
bkctld helps you manage the receiving side of a backup infrastructure.
It is licensed under the AGPLv3.
It uses SSH chroots (called "jails" in the FreeBSD world) to sandbox
every clients backups. Each client will upload it's data every day
using rsync in it's chroot (using the root account). Prior backups
are stored incrementally outside of the chroot using hard links or
BTRFS snapshots. (So they can not be affected by the client).
With bkctld you create and manage "jails". They contain a chrooted and dedicated SSH server, with it's own TCP port and optionnaly it's own set of iptables rules.
Using this method, we can keep a large quantity of backups of each
client securely and efficiently.
With bkctld you can have hundreds of jails, one for each client to push its data (using Rsync/SFTP). Each client can only see its own data.
In addition to the traditional "ext4" filesystem, bkctld also supports the btrfs filesystem and manages subvolumes automatically.
With bkctld you can create "timestamped" copies of the data, to keep different versions of the same data at different points in time. If the filesystem is btrfs, it creates subvolumes snapshots, otherwise it creates copies with hard-links (for file-level deduplication).
With btrfs you can have a data retention policy to automatically destroy timestamped copies of your data. For example, keep a copy for the last 5 days and the first day of the last 3 months.
~~~
Backup server
************
Server 1 ------ SSH/rsync -------> * tcp/2222 *
* *
Server 2 ------ SSH/rsync -------> * tcp/2223 *
Client 1 ------ SSH/Rsync -------> * tcp/2222 *
************
Client 2 ------ SSH/Rsync -------> * tcp/2223 *
************
~~~
This method uses standard tools (ssh, rsync, cp -al, btrfs subvolume)
and has been used for many years by Evolix to backup hundreds of
servers, totaling many terabytes of data, each day. bkctld has
been tested on Debian Jessie and should be compatible with other
Debian versions or derived distributions like Ubuntu.
This method uses standard tools (ssh, rsync, cp -al, btrfs subvolume) and has been used for many years by Evolix to backup hundreds of servers, totaling many terabytes of data, each day. bkctld has been tested on Debian Jessie (8), Stretch (9) and Buster (10) and should be compatible with other Debian versions or derived distributions like Ubuntu.
A large enough volume must be mounted on `/backup`, we recommend
the usage of **BTRFS** so you can use sub-volumes and snapshots.
A large enough volume must be mounted on `/backup`, we recommend the usage of **BTRFS** so you can use sub-volumes and snapshots.
This volume can also be encrypted with **LUKS**.
## Security considerations
The client obviously has access to its uploaded data (in the chroot), but the timestamped copies are outside the chroot, to reduce the risk or complete backup erasure from a compromised client.
Since the client connects to the backup server with root, it can mess with the jail and destroy the data. But the timestamped copies are out of reach because outside of the chroot.
It means that **if the client server is compromised**, an attacker can destroy the latest copy of the backed up data, but not the timestamped copies.
And **if the backup server is compromised** an attacker has complete access to all the backup data (inside and outside the jails), but they don't have any access to the client.
This architecture is as secure as SSH, Rsync, chroot and iptables are.
## Install
See the [installation guide](docs/install.md) for instructions.
@ -47,7 +53,7 @@ vagrant up
### Deployment
Launch rsync-auto in a terminal for automatic synchronization of
Run `vagrant rsync-auto` in a terminal for automatic synchronization of
your local code with Vagrant VM :
~~~
@ -57,12 +63,22 @@ vagrant rsync-auto
### Bats
You can run [bats](https://github.com/sstephenson/bats) tests with
the *test* provision :
the *test* provisioner :
~~~
vagrant provision --provision-with test
~~~
You can also run the tests from inside the VM
~~~
localhost $ vagrant ssh buster-btrfs
vagrant@buster-btrfs $ sudo -i
root@buster-btrfs # bats /vagrant/test/*.bats
~~~
You should shellcheck your bats files, but with shellcheck > 0.4.6, because the 0.4.0 version doesn't support bats syntax.
## Usage
See [docs/usage.md](docs/usage.md).
@ -82,8 +98,8 @@ pandoc -f markdown \
#### Client configuration
You can save various systems in the evobackup jails : Linux, BSD,
Windows, MacOSX. The only prerequisite is the rsync command.
You can backup various systems in the evobackup jails : Linux, BSD,
Windows, macOS. The only need Rsync or an SFTP client.
~~~
rsync -av -e "ssh -p SSH_PORT" /home/ root@SERVER_NAME:/var/backup/home/
@ -94,7 +110,7 @@ clone the evobackup repository and read the **CLIENT CONFIGURATION**
section of the manual.
~~~
git clone https://forge.evolix.org/evobackup.git
git clone https://gitea.evolix.org/evolix/evobackup.git
cd evobackup
man ./docs/bkctld.8
~~~

11
Vagrantfile vendored
View file

@ -28,7 +28,12 @@ DEBIAN_FRONTEND=noninteractive apt-get -yq install openssh-server btrfs-tools rs
SCRIPT
$pre_part = <<SCRIPT
lsof|awk '/backup/ { print $2 }'| xargs --no-run-if-empty kill -9
sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
sed -i -e 's/# fr_FR.UTF-8 UTF-8/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && \
echo 'LANG="fr_FR.UTF-8"'>/etc/default/locale && \
dpkg-reconfigure --frontend=noninteractive locales && \
update-locale LANG=fr_FR.UTF-8
lsof | awk '/backup/ { print $2 }' | xargs --no-run-if-empty kill -9
grep -q /backup /proc/mounts && umount -R /backup
exit 0
SCRIPT
@ -40,7 +45,9 @@ SCRIPT
nodes = [
{ :version => "stretch", :fs => "btrfs" },
{ :version => "stretch", :fs => "ext4" }
{ :version => "stretch", :fs => "ext4" },
{ :version => "buster", :fs => "btrfs" },
{ :version => "buster", :fs => "ext4" }
]
nodes.each do |i|

View file

@ -2,27 +2,28 @@
#
# Copyright (c) 2017 Victor Laborie <vlaborie@evolix.fr>
#
# shellcheck shell=bash
function _bkctld()
{
local cur prev commands jails keys
local cur prev commands jails keys
cur=${COMP_WORDS[COMP_CWORD]};
prev=${COMP_WORDS[COMP_CWORD-1]};
commands=$(find /usr/lib/bkctld/ -name "bkctld-*" -exec basename {} \;|sed 's/^bkctld-//')
cur=${COMP_WORDS[COMP_CWORD]};
prev=${COMP_WORDS[COMP_CWORD-1]};
commands=$(find /usr/lib/bkctld/ -name "bkctld-*" -exec basename {} \; | sed 's/^bkctld-//')
if [ $COMP_CWORD -eq 1 ]; then
COMPREPLY=($(compgen -W '${commands}' -- ${cur}))
elif [ $COMP_CWORD -eq 2 ]; then
[ -f /etc/default/bkctld ] && source /etc/default/bkctld
[ -z "${JAILDIR}" ] && JAILDIR='/backup/jails'
jails=$(ls "${JAILDIR}")
COMPREPLY=($(compgen -W "${jails}" -- ${cur}))
elif [ $COMP_CWORD -eq 3 ]; then
keys=$(ls *.pub)
COMPREPLY=($(compgen -W "${keys}" -- ${cur}))
fi
if [ $COMP_CWORD -eq 1 ]; then
COMPREPLY=($(compgen -W '${commands}' -- ${cur}))
elif [ $COMP_CWORD -eq 2 ]; then
[ -f /etc/default/bkctld ] && source /etc/default/bkctld
[ -z "${JAILDIR}" ] && JAILDIR='/backup/jails'
jails=$(ls "${JAILDIR}")
COMPREPLY=($(compgen -W "${jails}" -- ${cur}))
elif [ $COMP_CWORD -eq 3 ]; then
keys=$(ls *.pub)
COMPREPLY=($(compgen -W "${keys}" -- ${cur}))
fi
return 0
return 0
} &&
complete -F _bkctld bkctld

52
bkctld
View file

@ -3,8 +3,12 @@
# bkctld is a shell script to create and manage a backup server which will
# handle the backup of many servers (clients).
#
# Author: Victor Laborie <vlaborie@evolix.fr>
# Contributor: Benoît Série <bserie@evolix.fr>, Gregory Colpart <reg@evolix.fr>, Romain Dessort <rdessort@evolix.fr>, Tristan Pilat <tpilat@evolix.fr>
# Authors: Victor Laborie <vlaborie@evolix.fr>
# Jérémy Lecour <jlecour@evolix.fr>
# Benoît Série <bserie@evolix.fr>
# Gregory Colpart <reg@evolix.fr>
# Romain Dessort <rdessort@evolix.fr>
# Tristan Pilat <tpilat@evolix.fr>
# Licence: AGPLv3
#
@ -12,12 +16,22 @@ set -u
[ "$(id -u)" -ne 0 ] && error "You need to be root to run ${0} !"
[ -d './lib' ] && LIBDIR='lib'
[ -d '/usr/lib/bkctld' ] && LIBDIR='/usr/lib/bkctld'
. "${LIBDIR}/config"
basedir=$(dirname "$0")
if [ "${basedir}" = "/usr/local/sbin" ] && [ -d "/usr/local/lib/bkctld" ]; then
LIBDIR='/usr/local/lib/bkctld'
elif [ "${basedir}" = "/usr/sbin" ] && [ -d "/usr/lib/bkctld" ]; then
LIBDIR='/usr/lib/bkctld'
elif [ -d './lib' ]; then
LIBDIR='lib'
else
error "Failed to find a suitable lib directory for bkctld."
fi
# shellcheck source=lib/includes
. "${LIBDIR}/includes"
subcommand="${1:-}"
jail="${2:-}"
jail_name="${2:-}"
option="${3:-}"
if [ ! -x "${LIBDIR}/bkctld-${subcommand}" ]; then
@ -28,24 +42,24 @@ case "${subcommand}" in
"inc" | "rm" | "check" | "stats" | "help" | "list")
"${LIBDIR}/bkctld-${subcommand}"
;;
"init" | "is-on")
"${LIBDIR}/bkctld-${subcommand}" "${jail}"
"init" | "is-on")
"${LIBDIR}/bkctld-${subcommand}" "${jail_name}"
;;
"key" | "port" | "ip")
"${LIBDIR}/bkctld-${subcommand}" "${jail}" "${option}"
"${LIBDIR}/bkctld-${subcommand}" "${jail_name}" "${option}"
;;
"start" | "stop" | "reload" | "restart" | "sync" | "update" | "remove" | "firewall")
if [ "${jail}" = "all" ]; then
"${LIBDIR}/bkctld-list"|xargs --no-run-if-empty --max-args=1 --max-procs=0 "${LIBDIR}/bkctld-${subcommand}"
else
"${LIBDIR}/bkctld-${subcommand}" "${jail}"
fi
if [ "${jail_name}" = "all" ]; then
"${LIBDIR}/bkctld-list" | xargs --no-run-if-empty --max-args=1 --max-procs=0 "${LIBDIR}/bkctld-${subcommand}"
else
"${LIBDIR}/bkctld-${subcommand}" "${jail_name}"
fi
;;
"status")
if [ -z "${jail}" ]; then
"${LIBDIR}/bkctld-list"|xargs --no-run-if-empty --max-args=1 "${LIBDIR}/bkctld-${subcommand}"
else
"${LIBDIR}/bkctld-${subcommand}" "${jail}"
fi
if [ "${jail_name}" = "all" ] || [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-list" | xargs --no-run-if-empty --max-args=1 "${LIBDIR}/bkctld-${subcommand}"
else
"${LIBDIR}/bkctld-${subcommand}" "${jail_name}"
fi
;;
esac

View file

@ -22,25 +22,34 @@ rc=0
# loop for each configured jail
for file in ${EVOBACKUP_CONFIGS}; do
jail_name=$(basename ${file})
# check if jail is present
if jail_exists ${jail_name}; then
today=$(date +"%s")
# get jail last configuration date
jail_config_age=$(date --date "$(stat -c %y ${file})" +"%s")
# loop for each line in jail configuration
for line in $(cat ${file}); do
# inc date in ISO format
inc_date=$(relative_date ${line})
# inc date in seconds from epoch
inc_age=$(date --date "${inc_date}" +"%s")
# check if the configuration changed after the inc date
if [ "${jail_config_age}" -lt "${inc_age}" ]; then
# Error if inc is not found
if ! inc_exists ${jail_name}/${inc_date}*; then
echo "ERROR: inc is missing \`${jail_name}/${inc_date}'" >&2
rc=1
fi
else
# skip line if date is inthe future
if [ "${inc_age}" -gt "${today}" ]; then
echo "INFO: no inc expected for ${inc_date} \`${jail_name}'"
else
# check if the configuration changed after the inc date
if [ "${jail_config_age}" -lt "${inc_age}" ]; then
# Error if inc is not found
if ! inc_exists ${jail_name}/${inc_date}*; then
echo "ERROR: inc is missing \`${jail_name}/${inc_date}'" >&2
rc=1
fi
else
echo "INFO: no inc expected for ${inc_date} \`${jail_name}'"
fi
fi
done
else

View file

@ -1,5 +1,7 @@
# Install
## Install from package
A Debian package is available in the Evolix repository
~~~
@ -8,7 +10,27 @@ apt update
apt install bkctld
~~~
Then edit `/etc//bkctld`
Then edit `/etc/default/bkctld`
## Instal from sources
Warning: `cp`-ing the files without `-n` or `-i` will replace existing files !
~~~
# git clone https://gitea.evolix.org/evolix/evobackup.git
# cd evobackup
# cp bkctld /usr/local/sbin/
# mkdir -p /usr/local/lib/bkctld
# cp lib/* /usr/local/lib/bkctld/
# mkdir -p /usr/local/share/bkctld
# cp tpl/* /usr/local/share/bkctld/
# cp bkctld.service /lib/systemd/system/
# mkdir -p /usr/local/share/doc/bkctld
# cp zzz_evobackup /usr/local/share/doc/bkctld/
# mkdir -p /usr/local/share/bash_completion/
# cp bash_completion /usr/local/share/bash_completion/bkctld
# cp bkctld.conf /etc/default/bkctld
~~~
## Chroot dependencies
@ -30,7 +52,7 @@ apt install \
## Client dependencies
The clients only require OpenSSH and rsync.
The clients only require OpenSSH and Rsync.
### Cron job for incremental backups
@ -44,6 +66,6 @@ Edit the root crontab
~~~
## Notes
If you want mutiples backups in a day (1 by hour maximum) you can
If you want mutiples backups in a day (1 per hour maximum) you can
run `bkctld inc` multiples times, if you want to keep incremental
backups **for ever**, just don't run `bkctld rm`.

View file

@ -155,31 +155,31 @@ the backup server administrator will need:
He can then create the jail:
# bkctld init CLIENT_HOST_NAME
# bkctld key CLIENT_HOST_NAME /root/CLIENT_HOST_NAME.pub
# bkctld ip CLIENT_HOST_NAME CLIENT_IP_ADDRESS
# bkctld start CLIENT_HOST_NAME
# bkctld status CLIENT_HOST_NAME
# bkctld init <JAIL_NAME>
# bkctld key <JAIL_NAME> /root/<JAIL_NAME>.pub
# bkctld ip <JAIL_NAME> <IP_OR_CIDR>
# bkctld start <JAIL_NAME>
# bkctld status <JAIL_NAME>
And override the default
evobackup-incl(5)
rules
# $EDITOR /etc/evobackup/CLIENT_HOST_NAME
# $EDITOR /etc/evobackup/<JAIL_NAME>.d/incs_policy
To sync itself,
the client server will need to install
rsync(1).
It can then be run manually:
# rsync -av -e "ssh -p JAIL_PORT" /home/ root@BACKUP_SERVER:/var/backup/home/
# rsync -av -e "ssh -p <JAIL_PORT>" /home/ root@<BACKUP_SERVER>:/var/backup/home/
If a more automated setup is required,
a script can be written in any programming language.
In this case,
it may be useful to validate the backup server's identity before hand.
# ssh -p JAIL_PORT BACKUP_SERVER
# ssh -p <JAIL_PORT> root@<BACKUP_SERVER> -t exit
A
bash(1)

View file

@ -4,9 +4,9 @@
# Usage: check
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
cur_time=$(date "+%s")
return=0
nb_crit=0
nb_warn=0
@ -14,7 +14,10 @@ nb_ok=0
nb_unkn=0
output=""
# Check if the backup disk is properly mounted
if [ -b "${BACKUP_DISK}" ]; then
# If backup disk is encrypted, verify that it's open
cryptsetup isLuks "${BACKUP_DISK}"
if [ "$?" -eq 0 ]; then
if [ ! -b '/dev/mapper/backup' ]; then
@ -22,35 +25,77 @@ if [ -b "${BACKUP_DISK}" ]; then
echo "cryptsetup luksOpen ${BACKUP_DISK} backup"
exit 2
fi
# Change value to real device
BACKUP_DISK='/dev/mapper/backup'
fi
grep -qE "^${BACKUP_DISK} " /etc/mtab
# Verify that it's mounted and writable
findmnt --source ${BACKUP_DISK} -O rw > /dev/null
if [ "$?" -ne 0 ]; then
echo "Backup disk ${BACKUP_DISK} is not mounted !\n"
echo "Backup disk ${BACKUP_DISK} is not mounted (or read-only) !\n"
echo "mount ${BACKUP_DISK} /backup"
exit 2
fi
fi
for jail in $("${LIBDIR}/bkctld-list"); do
if [ -f "${JAILDIR}/${jail}/var/log/lastlog" ]; then
last_conn=$(stat --format=%Y "${JAILDIR}/${jail}/var/log/lastlog")
date_diff=$(( (cur_time - last_conn) / (60*60) ))
if [ "${date_diff}" -gt "${CRITICAL}" ]; then
nb_crit=$((nb_crit + 1))
output="${output}CRITICAL - ${jail} - ${date_diff} hours\n"
[ "${return}" -le 2 ] && return=2
elif [ "${date_diff}" -gt "${WARNING}" ]; then
nb_warn=$((nb_warn + 1))
output="${output}WARNING - ${jail} - ${date_diff} hours\n"
[ "${return}" -le 1 ] && return=1
else
nb_ok=$((nb_ok + 1))
output="${output}OK - ${jail} - ${date_diff} hours\n"
fi
# Check if the firewall file is sourced
minifirewall_config=/etc/default/minifirewall
if [ -n "${FIREWALL_RULES}" ] \
&& [ -r "${FIREWALL_RULES}" ] \
&& [ -f "${minifirewall_config}" ]; then
if ! grep -qE "^(\.|source) ${FIREWALL_RULES}" "${minifirewall_config}"; then
nb_warn=$((nb_warn + 1))
output="${output}WARNING - Firewall file '${FIREWALL_RULES}' doesn't seem to be sourced by '${minifirewall_config}'\n"
[ "${return}" -le 1 ] && return=1
fi
fi
# Check each jail status
check_jail() {
jail_name=$1
jail_path=$(jail_path "${jail_name}")
cur_time=$(date "+%s")
last_conn=$(stat --format=%Y "${jail_path}/var/log/lastlog")
date_diff=$(( (cur_time - last_conn) / (60*60) ))
check_policy_file=$(current_jail_check_policy_file "${jail_name}")
if [ -f "${check_policy_file}" ]; then
local_critical=$(read_variable "${check_policy_file}" "CRITICAL")
local_warning=$(read_variable "${check_policy_file}" "WARNING")
else
unset local_critical
unset local_warning
fi
# reset to default values if missing local value
: ${local_critical:=${CRITICAL}}
: ${local_warning:=${WARNING}}
if [ "${local_critical}" -gt "0" ] && [ "${date_diff}" -gt "${local_critical}" ]; then
nb_crit=$((nb_crit + 1))
output="${output}CRITICAL - ${jail_name} - ${date_diff} hours (${local_warning}/${local_critical})\n"
[ "${return}" -le 2 ] && return=2
elif [ "${local_warning}" -gt "0" ] && [ "${date_diff}" -gt "${local_warning}" ]; then
nb_warn=$((nb_warn + 1))
output="${output}WARNING - ${jail_name} - ${date_diff} hours (${local_warning}/${local_critical})\n"
[ "${return}" -le 1 ] && return=1
else
nb_ok=$((nb_ok + 1))
output="${output}OK - ${jail_name} - ${date_diff} hours (${local_warning}/${local_critical})\n"
fi
}
for jail_name in $(jails_list); do
jail_path=$(jail_path "${jail_name}")
if [ -f "${jail_path}/var/log/lastlog" ]; then
check_jail "${jail_name}"
else
nb_unkn=$((nb_unkn + 1))
output="${output}UNKNOWN - ${jail} doesn't have lastlog !\n"
output="${output}UNKNOWN - ${jail_name} doesn't have lastlog !\n"
[ "${return}" -le 3 ] && return=3
fi
done
@ -58,11 +103,11 @@ done
[ "${return}" -ge 0 ] && header="OK"
[ "${return}" -ge 1 ] && header="WARNING"
[ "${return}" -ge 2 ] && header="CRITICAL"
[ "${return}" -ge 3 ] && header="UNKNOW"
[ "${return}" -ge 3 ] && header="UNKNOWN"
printf "%s - %s UNK / %s CRIT / %s WARN / %s OK\n\n" "${header}" "${nb_unkn}" "${nb_crit}" "${nb_warn}" "${nb_ok}"
printf "${output}" | grep -E "^UNKNOW"
printf "${output}" | grep -E "^UNKNOWN"
printf "${output}" | grep -E "^CRITICAL"
printf "${output}" | grep -E "^WARNING"
printf "${output}" | grep -E "^OK"

View file

@ -4,21 +4,37 @@
# Usage: firewall <jailname>|all
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
jail_path=$(jail_path "${jail_name}")
iptables_input_accept() {
jail_name="${1}"
port="${2}"
ip="${3}"
echo "/sbin/iptables -A INPUT -p tcp --sport 1024: --dport ${port} -s ${ip} -j ACCEPT #${jail_name}"
}
if [ -n "${FIREWALL_RULES}" ]; then
[ -f "${FIREWALL_RULES}" ] && sed -i "/#${jail}$/d" "${FIREWALL_RULES}"
if [ -d "${JAILDIR}/${jail}" ]; then
port=$("${LIBDIR}/bkctld-port" "${jail}")
for ip in $("${LIBDIR}/bkctld-ip" "${jail}"); do
echo "/sbin/iptables -A INPUT -p tcp --sport 1024: --dport ${port} -s ${ip} -j ACCEPT #${jail}" >> "${FIREWALL_RULES}"
# remove existing rules for this jail
[ -f "${FIREWALL_RULES}" ] && sed -i "/#${jail_name}$/d" "${FIREWALL_RULES}"
if [ -d "${jail_path}" ]; then
port=$("${LIBDIR}/bkctld-port" "${jail_name}")
# Add a rule for each IP
for ip in $("${LIBDIR}/bkctld-ip" "${jail_name}"); do
iptables_input_accept "${jail_name}" "${port}" "${ip}" >> "${FIREWALL_RULES}"
done
# Restart the firewall
[ -f /etc/init.d/minifirewall ] && /etc/init.d/minifirewall restart >/dev/null
fi
notice "${jail} : firewall rules updated"
notice "${jail_name}: firewall rules have been updated."
else
warning "${jail_name}: skipping firewall update, FIREWALL_RULES variable is empty."
fi

View file

@ -4,7 +4,8 @@
# Usage: help
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
cat <<EOF
Usage: bkctld <subcommand> [options]

View file

@ -4,34 +4,92 @@
# Usage: inc
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
date=$(date +"%Y-%m-%d-%H")
for jail in $("${LIBDIR}/bkctld-list"); do
inc="${INCDIR}/${jail}/${date}"
mkdir -p "${INCDIR}/${jail}"
if [ ! -d "${inc}" ]; then
start=$(date +"%H:%M:%S")
jail_inode=$(stat --format=%i "${JAILDIR}/${jail}")
if [ "$jail_inode" -eq 256 ]; then
/bin/btrfs subvolume snapshot -r "${JAILDIR}/${jail}" "${inc}" | debug
end=$(date +"%H:%M:%S")
notice "${jail} : made ${date} inc [${start}/${end}]"
else
lock="/run/lock/bkctld/inc-${jail}.lock"
if [ -f "${lock}" ]; then
warning "${jail} : trying to run already running inc"
create_inc_btrfs() {
jail_name=$1
inc_name=$2
jail_path=$(jail_path "${jail_name}")
inc_path=$(inc_path "${jail_name}" "${inc_name}")
# The lock file prevents from starting a new copy when one is already being done
lock_file="${LOCKDIR}/inc-${jail_name}-${inc_name}.lock"
if [ -f "${lock_file}" ]; then
warning "${jail_name}: skipping '${inc_name}', it is already being created."
else
(
start=$(current_time)
mkdir --parents "${LOCKDIR}" && touch "${lock_file}"
# shellcheck disable=SC2064
trap "rm -f ${lock_file}" 0
if dry_run; then
echo "[dry-run] btrfs subvolume snapshot of ${jail_path} to ${inc_path}"
else
(
mkdir -p /run/lock/bkctld && touch "${lock}"
trap "rm -f ${lock}" 0
cp -alx "${JAILDIR}/${jail}/" "${inc}"
end=$(date +"%H:%M:%S")
notice "${jail} : made ${date} inc [${start}/${end}]"
)
mkdir --parents "$(dirname "${inc_path}")"
# create a btrfs readonly snapshot from the jail
/bin/btrfs subvolume snapshot -r "${jail_path}" "${inc_path}" | debug
fi
end=$(current_time)
notice "${jail_name}: inc '${inc_name}' has been created [${start}/${end}]"
)
fi
}
create_inc_ext4() {
jail_name=$1
inc_name=$2
jail_path=$(jail_path "${jail_name}")
inc_path=$(inc_path "${jail_name}" "${inc_name}")
# The lock file prevents from starting a new copy when one is already being done
lock_file="${LOCKDIR}/inc-${jail_name}-${inc_name}.lock"
if [ -f "${lock_file}" ]; then
warning "${jail_name}: skipping '${inc_name}', it is already being created."
else
(
start=$(current_time)
mkdir --parents "${LOCKDIR}" && touch "${lock_file}"
# shellcheck disable=SC2064
trap "rm -f ${lock_file}" 0
if dry_run; then
echo "[dry-run] copy of ${jail_path} to ${inc_path}"
else
mkdir --parents "$(dirname "${inc_path}")"
# create a copy of the jail with hard links
cp --archive --link --one-file-system "${jail_path}/" "${inc_path}"
fi
end=$(current_time)
notice "${jail_name}: in '${inc_name}' has been created [${start}/${end}]"
)
fi
}
inc_name=$(date +"%Y-%m-%d-%H")
for jail_name in $(jails_list); do
jail_path=$(jail_path "${jail_name}")
inc_path=$(inc_path "${jail_name}" "${inc_name}")
incs_policy_file=$(current_jail_incs_policy_file ${jail_name})
# If no incs policy is found, we don't create incs
if [ -n "${incs_policy_file}" ]; then
# If not incs directory is found, we don't create incs
if [ ! -d "${inc_path}" ]; then
if is_btrfs "${jail_path}"; then
create_inc_btrfs "${jail_name}" "${inc_name}"
else
create_inc_ext4 "${jail_name}" "${inc_name}"
fi
else
warning "${jail_name}: skipping ${inc_name}, it already exists."
fi
else
warning "${jail} : trying to made already existant inc"
warning "${jail_name}: skipping ${inc_name}, incs policy not found."
fi
done

View file

@ -4,32 +4,28 @@
# Usage: init <jailname>
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] && error "${jail} : trying to create existant jail"
jail_path=$(jail_path "${jail_name}")
mkdir -p "${CONFDIR}" "${JAILDIR}"
sshd_config="${TPLDIR}/sshd_config"
inctpl="${TPLDIR}/inc.tpl"
[ -f "${LOCALTPLDIR}/sshd_config" ] && sshd_config="${LOCALTPLDIR}/sshd_config"
[ -f "${LOCALTPLDIR}/inc.tpl" ] && inctpl="${LOCALTPLDIR}/inc.tpl"
test -d "${jail_path}" && error "${jail_name}: jail already exists."
rootdir=$(dirname "${JAILDIR}")
rootdir_inode=$(stat --format=%i "${rootdir}")
jaildir_inode=$(stat --format=%i "${JAILDIR}")
if [ "${rootdir_inode}" -eq 256 ] || [ "${jaildir_inode}" -eq 256 ]; then
/bin/btrfs subvolume create "${JAILDIR}/${jail}"
# Create config and jails directory
mkdir --parents "${CONFDIR}" "${JAILDIR}"
if is_btrfs "$(dirname "${JAILDIR}")" || is_btrfs "${JAILDIR}"; then
/bin/btrfs subvolume create "${jail_path}"
else
mkdir -p "${JAILDIR}/${jail}"
mkdir --parents "${jail_path}"
fi
. "${LIBDIR}/mkjail"
info "4 - Copie default sshd_config"
install -m 0640 "${sshd_config}" "${JAILDIR}/${jail}/${SSHD_CONFIG}"
info "5 - Copie default inc configuration"
install -m 0640 "${inctpl}" "${CONFDIR}/${jail}"
"${LIBDIR}/bkctld-port" "${jail}" auto
notice "${jail} : created jail"
setup_jail_chroot "${jail_name}"
setup_jail_config "${jail_name}"
notice "${jail_name}: jail has been created"

View file

@ -4,32 +4,45 @@
# Usage: ip <jailname> [<ip>|all]
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
jail_name="${1:?}"
ip="${2:-}"
if [ ! -n "${jail}" ]; then
if [ ! -n "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : inexistant jail'"
jail_path=$(jail_path "${jail_name}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
jail_sshd_config="${jail_path}/${SSHD_CONFIG}"
if [ -z "${ip}" ]; then
grep -E "^AllowUsers" "${JAILDIR}/$jail/${SSHD_CONFIG}"|grep -Eo "root@[^ ]+"| while read allow; do
echo "${allow}"|cut -d'@' -f2
# parse IP addresses from AllowUsers directives in sshd config
grep -E "^AllowUsers" "${jail_sshd_config}" \
| grep -E -o "root@[^ ]+" \
| while read allow; do
echo "${allow}" | cut -d'@' -f2
done
else
if [ "${ip}" = "all" ] || [ "${ip}" = "0.0.0.0/0" ]; then
ips="0.0.0.0/0"
new_ips="0.0.0.0/0"
else
ips=$("${LIBDIR}/bkctld-ip" "${jail}")
ips=$(echo "${ips}" "${ip}"|xargs -n1|grep -v "0.0.0.0/0"|sort|uniq)
existing_ips=$("${LIBDIR}/bkctld-ip" "${jail_name}")
new_ips=$(echo "${existing_ips}" "${ip}" | xargs -n1 | grep -v "0.0.0.0/0" | sort | uniq)
fi
allow="AllowUsers"
for ip in $ips; do
allow="${allow} root@${ip}"
allow_users="AllowUsers"
for ip in ${new_ips}; do
allow_users="${allow_users} root@${ip}"
done
sed -i "s~^AllowUsers .*~${allow}~" "${JAILDIR}/$jail/${SSHD_CONFIG}"
notice "${jail} : update ip => ${ip}"
"${LIBDIR}/bkctld-reload" "${jail}"
"${LIBDIR}/bkctld-firewall" "${jail}"
if grep -q -E "^AllowUsers" "${jail_sshd_config}"; then
sed -i "s~^AllowUsers .*~${allow_users}~" "${jail_sshd_config}"
else
error "${jail_name}: No 'AllowUsers' directive found in '${jail_sshd_config}'"
fi
notice "${jail_name}: IP whitelist updated to ${ip}"
"${LIBDIR}/bkctld-reload" "${jail_name}"
"${LIBDIR}/bkctld-firewall" "${jail_name}"
fi

View file

@ -4,23 +4,28 @@
# Usage: is-on <jailname>
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to check inexistant jail"
jail_path=$(jail_path "${jail_name}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
jail_pid_file="${jail_path}/${SSHD_PID}"
jail="${1}"
return=1
if [ -f "${JAILDIR}/${jail}/${SSHD_PID}" ]; then
pid=$(cat "${JAILDIR}/${jail}/${SSHD_PID}")
if [ -f "${jail_pid_file}" ]; then
pid=$(cat "${jail_pid_file}")
ps -p "${pid}" > /dev/null && return=0
fi
if [ "${return}" -eq 1 ]; then
rm -f "${JAILDIR}/${jail}/${SSHD_PID}"
grep -q "${JAILDIR}/${jail}/proc" /proc/mounts && umount --lazy "${JAILDIR}/${jail}/proc/"
grep -q "${JAILDIR}/${jail}/dev" /proc/mounts && umount --lazy --recursive "${JAILDIR}/${jail}/dev"
rm -f "${jail_pid_file}"
grep -q "${jail_path}/proc" /proc/mounts && umount --lazy "${jail_path}/proc/"
grep -q "${jail_path}/dev" /proc/mounts && umount --lazy --recursive "${jail_path}/dev"
fi
exit "${return}"

View file

@ -4,22 +4,26 @@
# Usage: key <jailname> [<keyfile>]
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
jail_name="${1:?}"
keyfile="${2:-}"
if [ ! -n "${jail}" ]; then
if [ ! -n "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : inexistant jail'"
jail_path=$(jail_path "${jail_name}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
if [ -z "${keyfile}" ]; then
if [ -f "${JAILDIR}/${jail}/${AUTHORIZED_KEYS}" ]; then
cat "${JAILDIR}/${jail}/${AUTHORIZED_KEYS}"
if [ -f "${jail_path}/${AUTHORIZED_KEYS}" ]; then
cat "${jail_path}/${AUTHORIZED_KEYS}"
fi
else
[ -e "${keyfile}" ] || error "Keyfile ${keyfile} dosen't exist !"
cat "${keyfile}" > "${JAILDIR}/${jail}/${AUTHORIZED_KEYS}"
chmod 600 "${JAILDIR}/${jail}/${AUTHORIZED_KEYS}"
notice "${jail} : update key => ${keyfile}"
test -r "${keyfile}" || error "${jail_name}: SSH key '${keyfile}' is missing or is not readable."
cat "${keyfile}" > "${jail_path}/${AUTHORIZED_KEYS}"
chmod 600 "${jail_path}/${AUTHORIZED_KEYS}"
notice "${jail_name}: SSH key has been updated with ${keyfile}"
fi

View file

@ -6,7 +6,10 @@
set -eu
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
[ -d "${JAILDIR}" ] || exit 0
#TODO: try if this command works the same :
# find "${JAILDIR}" -mindepth 1 -maxdepth 1 -type d -printf '%f\n'
find "${JAILDIR}" -mindepth 1 -maxdepth 1 -type d|sed 's!.*/!!'

View file

@ -4,25 +4,33 @@
# Usage: port <jailname> [<port>|auto]
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
jail_name="${1:?}"
port="${2:-}"
if [ ! -n "${jail}" ]; then
if [ ! -n "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : inexistant jail'"
jail_path=$(jail_path "${jail_name}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
jail_sshd_config="${jail_path}/${SSHD_CONFIG}"
if [ -z "${port}" ]; then
grep -E "Port [0-9]+" "${JAILDIR}/${jail}/${SSHD_CONFIG}"|grep -oE "[0-9]+"
grep -E "Port [0-9]+" "${jail_sshd_config}"|grep -oE "[0-9]+"
else
if [ "${port}" = "auto" ]; then
port=$(grep -h Port "${JAILDIR}"/*/"${SSHD_CONFIG}" 2>/dev/null | grep -Eo "[0-9]+" | sort -n | tail -1)
port=$((port+1))
[ "${port}" -le 1 ] && port=2222
fi
sed -i "s/^Port .*/Port ${port}/" "${JAILDIR}/$jail/${SSHD_CONFIG}"
notice "${jail} : update port => ${port}"
"${LIBDIR}/bkctld-reload" "${jail}"
"${LIBDIR}/bkctld-firewall" "${jail}"
sed -i "s/^Port .*/Port ${port}/" "${jail_sshd_config}"
notice "${jail_name}: port has been updated to ${port}"
"${LIBDIR}/bkctld-reload" "${jail_name}"
"${LIBDIR}/bkctld-firewall" "${jail_name}"
fi

View file

@ -4,15 +4,19 @@
# Usage: reload <jailname>|all
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to reload inexistant jail"
"${LIBDIR}/bkctld-is-on" "${jail}" || exit 0
jail_path=$(jail_path "${jail_name}")
pid=$(cat "${JAILDIR}/${jail}/${SSHD_PID}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
kill -HUP "${pid}" && notice "${jail} was reloaded [${pid}]"
"${LIBDIR}/bkctld-is-on" "${jail_name}" || exit 0
pid=$(cat "${jail_path}/${SSHD_PID}")
kill -HUP "${pid}" && notice "${jail_name}: jail has been reloaded [${pid}]"

View file

@ -4,33 +4,39 @@
# Usage: remove <jailname>|all
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to remove inexistant jail"
"${LIBDIR}/bkctld-is-on" "${jail}" && "${LIBDIR}/bkctld-stop" "${jail}"
jail_path=$(jail_path "${jail_name}")
incs_path=$(incs_path "${jail_name}")
rm -f "${CONFDIR}/${jail}"
jail_inode=$(stat --format=%i "${JAILDIR}/${jail}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
"${LIBDIR}/bkctld-is-on" "${jail_name}" && "${LIBDIR}/bkctld-stop" "${jail_name}"
rm -f "${CONFDIR}/${jail_name}"
jail_inode=$(stat --format=%i "${jail_path}")
if [ "${jail_inode}" -eq 256 ]; then
/bin/btrfs subvolume delete "${JAILDIR}/${jail}" | debug
/bin/btrfs subvolume delete "${jail_path}" | debug
else
rm -rf "${JAILDIR}/${jail}" | debug
rm -rf "${jail_path:?}" | debug
fi
if [ -d "${INCDIR}/${jail}" ]; then
incs=$(ls "${INCDIR}/${jail}")
# TODO: use functions here
if [ -d "${incs_path}" ]; then
incs=$(ls "${incs_path}")
for inc in ${incs}; do
inc_inode=$(stat --format=%i "${INCDIR}/${jail}/${inc}")
inc_inode=$(stat --format=%i "${incs_path}/${inc}")
if [ "${inc_inode}" -eq 256 ]; then
/bin/btrfs subvolume delete "${INCDIR}/${jail}/${inc}" | debug
/bin/btrfs subvolume delete "${incs_path}/${inc}" | debug
else
warning "You need to purge ${INCDIR}/${jail}/${inc} manually !"
warning "You need to purge ${incs_path}/${inc} manually !"
fi
done
rmdir --ignore-fail-on-non-empty "${INCDIR}/${jail}" | debug
rmdir --ignore-fail-on-non-empty "${incs_path}" | debug
fi
"${LIBDIR}/bkctld-firewall" "${jail}"
notice "${jail} : deleted jail"
"${LIBDIR}/bkctld-firewall" "${jail_name}"
notice "${jail_name}: jail has been deleted."

View file

@ -6,12 +6,16 @@
set -eu
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to restart inexistant jail"
"${LIBDIR}/bkctld-is-on" "${jail}" && "${LIBDIR}/bkctld-stop" "${jail}"
"${LIBDIR}/bkctld-start" "${jail}"
jail_path=$(jail_path "${jail_name}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
"${LIBDIR}/bkctld-is-on" "${jail_name}" && "${LIBDIR}/bkctld-stop" "${jail_name}"
"${LIBDIR}/bkctld-start" "${jail_name}"

View file

@ -4,41 +4,109 @@
# Usage: rm
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
for jail in $("${LIBDIR}/bkctld-list"); do
incs=$(ls "${INCDIR}/${jail}")
if [ -f "${CONFDIR}/${jail}" ]; then
keepfile="$(mktemp)"
while read j; do
date=$( echo "${j}" | cut -d. -f1 )
before=$( echo "${j}" | cut -d. -f2 )
date -d "$(date "${date}") ${before}" "+%Y-%m-%d"
done < "${CONFDIR}/${jail}" > "${keepfile}"
for j in $(echo "${incs}" | grep -v -f "${keepfile}"); do
start=$(date +"%H:%M:%S")
inc_inode=$(stat --format=%i "${INCDIR}/${jail}/${j}")
if [ "${inc_inode}" -eq 256 ]; then
/bin/btrfs subvolume delete "${INCDIR}/${jail}/${j}" | debug
end=$(date +"%H:%M:%S")
notice "${jail} : deleted ${j} inc [${start}/${end}]"
else
lock="/run/lock/bkctld/rm-${jail}.lock"
if [ -f "${lock}" ]; then
warning "${jail} : trying to run already running rm"
else
(
empty="/tmp/bkctld-${$}-$(date +%N)"
mkdir -p /run/lock/bkctld && touch "${lock}" && mkdir -p "${empty}"
trap "rm -f ${lock} && rmdir ${empty}" 0
rsync -a --delete "${empty}/" "${INCDIR}/${jail}/${j}/"
rmdir "${INCDIR}/${jail}/${j}/"
end=$(date +"%H:%M:%S")
notice "${jail} : deleted ${j} inc [${start}/${end}]"
)
fi
fi
relative_date() {
format=$(echo $1 | cut -d'.' -f1)
time_jump=$(echo $1 | cut -d'.' -f2)
reference_date=$(date "${format}")
past_date=$(date --date "${reference_date} ${time_jump}" +"%Y-%m-%d")
echo ${past_date}
}
delete_inc_btrfs() {
jail_name=$1
inc_name=$2
inc_path=$(inc_path "${jail_name}" "${inc_name}")
start=$(current_time)
if dry_run; then
echo "[dry-run] delete btrfs subvolume ${inc_path}"
else
/bin/btrfs subvolume delete "${inc_path}" | debug
fi
end=$(current_time)
notice "${jail_name}: inc '${inc_name}' has been deleted [${start}/${end}]"
}
delete_inc_ext4() {
jail_name=$1
inc_name=$2
inc_path=$(inc_path "${jail_name}" "${inc_name}")
lock_file="${LOCKDIR}/rm-global.lock"
if [ -f "${lock_file}" ]; then
# Get Process ID from the lock file
pid=$(cat "${lock_file}")
if kill -0 ${pid} 2> /dev/null; then
# Kill the children
pkill -9 --parent "${pid}"
# Kill the parent
kill -9 "${pid}"
# Remove the lock file
rm -f ${lock_file}
warning "Process ${pid} has been killed. Only one ${0} can run in parallel, the latest wins."
else
error "Empty lockfile '${lock_file}'. It should contain a PID."
fi
fi
mkdir --parents "${LOCKDIR}" && echo $$ > ${lock_file} || error "Failed to acquire lock file '${lock_file}'"
empty=$(mktemp -d --suffix ".${$}" bkctld.XXXXX)
# shellcheck disable=SC2064
trap "rm -f ${lock_file}; rmdir ${empty}" 0
if dry_run; then
echo "[dry-run] delete ${inc_path} with rsync from ${empty}"
else
rsync --archive --delete "${empty}/" "${inc_path}/"
rmdir "${inc_path}/"
fi
end=$(current_time)
notice "${jail_name}: inc '${inc_name}' has been deleted [${start}/${end}]"
}
for jail_name in $(jails_list); do
incs_policy_file=$(current_jail_incs_policy_file ${jail_name})
# If not incs policy if found, we don't remove incs
if [ -n "${incs_policy_file}" ]; then
incs_policy_keep_file="$(mktemp)"
incs_list_file="$(mktemp)"
# shellcheck disable=SC2064
trap "rm -f ${incs_policy_keep_file} ${incs_list_file}" 0
# loop for each line in jail configuration
for incs_policy_line in $(grep "^\+" ${incs_policy_file}); do
# inc date in ISO format
incs_policy_date=$(relative_date ${incs_policy_line})
echo ${incs_policy_date} >> "${incs_policy_keep_file}"
done
rm "${keepfile}"
for inc_name in $(incs_list "${jail_name}"); do
echo "${inc_name}" >> ${incs_list_file}
done
# shellcheck disable=SC2046
incs_to_delete=$(grep -v -f "${incs_policy_keep_file}" "${incs_list_file}")
if [ -n "${incs_to_delete}" ]; then
debug "${jail_name}: incs to be deleted : $(echo "${incs_to_delete}" | tr '\n', ',' | sed 's/,$//')."
for inc_name in ${incs_to_delete}; do
inc_path=$(inc_path "${jail_name}" "${inc_name}")
if is_btrfs "${inc_path}"; then
delete_inc_btrfs "${jail_name}" "${inc_name}"
else
delete_inc_ext4 "${jail_name}" "${inc_name}"
fi
done
else
notice "${jail_name}: no inc to be deleted."
fi
fi
done

View file

@ -4,39 +4,40 @@
# Usage: start <jailname>|all
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to start inexistant jail"
"${LIBDIR}/bkctld-is-on" "${jail}" && exit 0
jail_path=$(jail_path "${jail_name}")
cd "${JAILDIR}/${jail}"
grep -q "${JAILDIR}/${jail}/proc" /proc/mounts || mount -t proc "proc-${jail}" proc
grep -q "${JAILDIR}/${jail}/dev" /proc/mounts || mount -nt tmpfs "dev-${jail}" dev
[ -e "dev/console" ] || mknod -m 622 dev/console c 5 1
[ -e "dev/null" ] || mknod -m 666 dev/null c 1 3
[ -e "dev/zero" ] || mknod -m 666 dev/zero c 1 5
[ -e "dev/ptmx" ] || mknod -m 666 dev/ptmx c 5 2
[ -e "dev/tty" ] || mknod -m 666 dev/tty c 5 0
[ -e "dev/random" ] || mknod -m 444 dev/random c 1 8
[ -e "dev/urandom" ] || mknod -m 444 dev/urandom c 1 9
chown root:tty dev/console dev/ptmx dev/tty
ln -fs proc/self/fd dev/fd
ln -fs proc/self/fd/0 dev/stdin
ln -fs proc/self/fd/1 dev/stdout
ln -fs proc/self/fd/2 dev/stderr
ln -fs proc/kcore dev/core
mkdir -p dev/pts
mkdir -p dev/shm
grep -q "${JAILDIR}/${jail}/dev/pts" /proc/mounts || mount -t devpts -o gid=4,mode=620 none dev/pts
grep -q "${JAILDIR}/${jail}/dev/shm" /proc/mounts || mount -t tmpfs none dev/shm
chroot "${JAILDIR}/${jail}" /usr/sbin/sshd -E /var/log/authlog || error "${jail} : error on starting sshd"
pidfile="${JAILDIR}/${jail}/${SSHD_PID}"
for try in {1..10}; do
[ -f "${pidfile}" ] || sleep 0.3
test -d "${jail_path}" || error "${jail_name}: jail is missing."
"${LIBDIR}/bkctld-is-on" "${jail_name}" && exit 0
# Prepare the chroot
mount_jail_fs "${jail_name}"
# Start SSH in the chroot
chroot "${jail_path}" /usr/sbin/sshd -E /var/log/authlog || error "${jail_name}: failed to start SSH."
pidfile="${jail_path}/${SSHD_PID}"
# Wait for SSH to be up
# shellcheck disable=SC2034
for try in $(seq 1 10); do
if [ -f "${pidfile}" ]; then
pid=$(cat "${pidfile}")
break
else
pid=""
sleep 0.3
fi
done
pid=$(cat "${pidfile}")
notice "${jail} was started [${pid}]"
if [ -n "${pid}" ]; then
notice "${jail_name}: jail has been started [${pid}]"
else
error "${jail_name}: failed to fetch SSH pid within 3 sec."
fi

View file

@ -4,31 +4,45 @@
# Usage: stats
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
mkdir -p "${INDEX_DIR}"
lsof "${IDX_FILE}" >/dev/null 2>&1 || nohup sh -s -- <<EOF >/dev/null 2>&1 &
ionice -c3 "${DUC}" index -d "${IDX_FILE}" "${JAILDIR}"
touch "${INDEX_DIR}/.lastrun.duc"
EOF
[ ! -f "${INDEX_DIR}/.lastrun.duc" ] && notice "First run of DUC always in progress ..." && exit 0
[ ! -f ${IDX_FILE} ] && error "Index file do not exits !"
printf "Last update of index file : "
stat --format=%Y "${INDEX_DIR}/.lastrun.duc" | xargs -i -n1 date -R -d "@{}"
echo "<jail> <size> <incs> <lastconn>" | awk '{ printf("%- 30s %- 10s %- 10s %- 15s\n", $1, $2, $3, $4); }'
duc_output=$(mktemp)
stat_output=$(mktemp)
incs_output=$(mktemp)
# shellcheck disable=SC2064
trap "rm ${duc_output} ${incs_output} ${stat_output}" 0
"${DUC}" ls -d "${IDX_FILE}" "${JAILDIR}" > "${duc_output}"
awk '{ print $2 }' "${duc_output}" | while read jail; do
stat --format=%Y "/backup/jails/${jail}/var/log/lastlog" | xargs -i -n1 date -d "@{}" "+%d-%m-%Y" >> "${stat_output}"
inc=0
if [ -f "${CONFDIR}/${jail}" ]; then
day=$(grep -c "day" "${CONFDIR}/${jail}")
month=$(grep -c "month" "${CONFDIR}/${jail}")
inc="${day}/${month}"
awk '{ print $2 }' "${duc_output}" | while read jail_name; do
jail_path=$(jail_path "${jail_name}")
stat --format=%Y "${jail_path}/var/log/lastlog" | xargs -i -n1 date -d "@{}" "+%d-%m-%Y" >> "${stat_output}"
incs_policy_file=$(current_jail_incs_policy_file ${jail_name})
incs_policy="0"
if [ -r "${incs_policy_file}" ]; then
days=$(grep "^\+" "${incs_policy_file}" | grep --count "day")
months=$(grep "^\+" "${incs_policy_file}" | grep --count "month")
incs_policy="${days}/${months}"
fi
echo "${inc}" >> "${incs_output}"
echo "${incs_policy}" >> "${incs_output}"
done
paste "${duc_output}" "${incs_output}" "${stat_output}" | awk '{ printf("%- 30s %- 10s %- 10s %- 15s\n", $2, $1, $3, $4); }'

View file

@ -4,22 +4,27 @@
# Usage: status [<jailname>]
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ ! -n "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : inexistant jail ! Use '$0 status' for list all"
[ -d "${JAILDIR}/${jail_name}" ] || error "${jail_name} : jail is missing.\nUse '$0 status [all]' to get the status of all jails."
inc="0"
if [ -f "${CONFDIR}/${jail}" ]; then
day=$(grep -c "day" "${CONFDIR}/${jail}")
month=$(grep -c "month" "${CONFDIR}/${jail}")
inc="${day}/${month}"
incs_policy_file=$(current_jail_incs_policy_file ${jail_name})
incs_policy="0"
if [ -r "${incs_policy_file}" ]; then
days=$(grep "^\+" "${incs_policy_file}" | grep --count "day")
months=$(grep "^\+" "${incs_policy_file}" | grep --count "month")
incs_policy="${days}/${months}"
fi
status="OFF"
"${LIBDIR}/bkctld-is-on" "${jail}" && status="ON "
port=$("${LIBDIR}/bkctld-port" "${jail}")
ip=$("${LIBDIR}/bkctld-ip" "${jail}"|xargs|tr -s ' ' ',')
echo "${jail} ${status} ${port} ${inc} ${ip}" | awk '{ printf("%- 30s %- 10s %- 10s %- 10s %- 40s\n", $1, $2, $3, $4, $5); }'
"${LIBDIR}/bkctld-is-on" "${jail_name}" && status="ON "
port=$("${LIBDIR}/bkctld-port" "${jail_name}")
ip=$("${LIBDIR}/bkctld-ip" "${jail_name}" | xargs | tr -s ' ' ',')
echo "${jail_name} ${status} ${port} ${incs_policy} ${ip}" | awk '{ printf("%- 30s %- 10s %- 10s %- 10s %- 40s\n", $1, $2, $3, $4, $5); }'

View file

@ -4,19 +4,28 @@
# Usage: stop <jailname>|all
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to stop inexistant jail"
"${LIBDIR}/bkctld-is-on" "${jail}" || exit 0
jail_path=$(jail_path "${jail_name}")
pid=$(cat "${JAILDIR}/${jail}/${SSHD_PID}")
for conn in $(ps --ppid "${pid}" -o pid=); do
kill "${conn}"
done
kill "${pid}" && notice "${jail} was stopped [${pid}]"
umount --lazy --recursive "${JAILDIR}/${jail}/dev"
umount --lazy "${JAILDIR}/${jail}/proc/"
test -d "${jail_path}" || error "${jail_name}: jail is missing."
"${LIBDIR}/bkctld-is-on" "${jail_name}" || exit 0
pid=$(cat "${jail_path}/${SSHD_PID}")
pkill --parent "${pid}"
if kill "${pid}"; then
notice "${jail_name}: jail has been stopped [${pid}]"
umount --lazy --recursive "${jail_path}/dev"
umount --lazy "${jail_path}/proc/"
else
error "${jail_name}: failed to stop jail [${pid}]"
fi

View file

@ -4,21 +4,37 @@
# Usage: sync <jailname>|all
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ -z "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to sync inexistant jail"
jail_path=$(jail_path "${jail_name}")
jail_config_dir=$(jail_config_dir "${jail_name}")
test -d "${jail_path}" || error "${jail_name}: jail is missing."
[ -n "${NODE}" ] || error "Sync need config of \$NODE in /etc/default/bkctld !"
jail="${1}"
ssh "${NODE}" "${LIBDIR}/bkctld-init" "${jail}" | debug
rsync -a "${JAILDIR}/${jail}/" "${NODE}:${JAILDIR}/${jail}/" --exclude proc/* --exclude sys/* --exclude dev/* --exclude run --exclude var/backup/*
rsync -a "${CONFDIR}/${jail}" "${NODE}:${CONFDIR}/${jail}"
"${LIBDIR}/bkctld-is-on" "${jail}" && ssh "${NODE}" "${LIBDIR}/bkctld-start" "${jail}" | debug
# Init jail on remote server
ssh "${NODE}" "${LIBDIR}/bkctld-init" "${jail_name}" | debug
# Sync Jail structure and configuration on remote server
rsync -a "${jail_path}/" "${NODE}:${jail_path}/" --exclude proc/* --exclude sys/* --exclude dev/* --exclude run --exclude var/backup/*
# New config directory
rsync -a "${jail_config_dir}" "${NODE}:${jail_config_dir}"
# Old incs policy config file
rsync -a "${CONFDIR}/${jail_name}" "${NODE}:${CONFDIR}/${jail_name}"
# Sync state on remote server
if "${LIBDIR}/bkctld-is-on" "${jail_name}"; then
ssh "${NODE}" "${LIBDIR}/bkctld-start" "${jail_name}" | debug
else
ssh "${NODE}" "${LIBDIR}/bkctld-stop" "${jail_name}" | debug
fi
if [ -n "${FIREWALL_RULES}" ]; then
rsync -a "${FIREWALL_RULES}" "${NODE}:${FIREWALL_RULES}"
ssh "${NODE}" /etc/init.d/minifirewall restart | debug

View file

@ -4,14 +4,19 @@
# Usage: update <jailname>|all
#
LIBDIR="$(dirname $0)" && . "${LIBDIR}/config"
# shellcheck source=./includes
LIBDIR="$(dirname $0)" && . "${LIBDIR}/includes"
jail="${1:-}"
if [ ! -n "${jail}" ]; then
jail_name="${1:?}"
if [ ! -n "${jail_name}" ]; then
"${LIBDIR}/bkctld-help" && exit 1
fi
[ -d "${JAILDIR}/${jail}" ] || error "${jail} : trying to update inexistant jail"
"${LIBDIR}/bkctld-is-on" "${jail}" && "${LIBDIR}/bkctld-stop" "${jail}"
jail_path=$(jail_path "${jail_name}")
. "${LIBDIR}/mkjail"
notice "${jail} : updated jail"
test -d "${jail_path}" || error "${jail_name}: jail is missing."
"${LIBDIR}/bkctld-is-on" "${jail_name}" && "${LIBDIR}/bkctld-stop" "${jail_name}"
setup_jail_chroot "${jail_name}"
notice "${jail_name}: jail has been updated."

View file

@ -1,64 +0,0 @@
#!/bin/sh
#
# Config for bkctld
#
[ -f /etc/default/bkctld ] && . /etc/default/bkctld
LIBDIR=${LIBDIR:-/usr/lib/bkctld}
CONFDIR="${CONFDIR:-/etc/evobackup}"
BACKUP_DISK="${BACKUP_DISK:-}"
JAILDIR="${JAILDIR:-/backup/jails}"
INCDIR="${INCDIR:-/backup/incs}"
TPLDIR="${TPLDIR:-/usr/share/bkctld}"
INDEX_DIR="${INDEX_DIR:-/backup/index}"
IDX_FILE="${IDX_FILE:-${INDEX_DIR}/bkctld-jails.idx}"
LOCALTPLDIR="${LOCALTPLDIR:-/usr/local/share/bkctld}"
SSHD_PID="${SSHD_PID:-/run/sshd.pid}"
SSHD_CONFIG="${SSHD_CONFIG:-/etc/ssh/sshd_config}"
AUTHORIZED_KEYS="${AUTHORIZED_KEYS:-/root/.ssh/authorized_keys}"
FIREWALL_RULES="${FIREWALL_RULES:-}"
LOGLEVEL="${LOGLEVEL:-6}"
CRITICAL="${CRITICAL:-48}"
WARNING="${WARNING:-24}"
DUC=$(command -v duc-nox||command -v duc)
debug() {
msg="${1:-$(cat /dev/stdin)}"
if [ "${LOGLEVEL}" -ge 7 ]; then
echo "${msg}"
logger -t bkctld -p daemon.debug "${msg}"
fi
}
info() {
msg="${1:-$(cat /dev/stdin)}"
if [ "${LOGLEVEL}" -ge 6 ]; then
tty -s && echo "${msg}"
logger -t bkctld -p daemon.info "${msg}"
fi
}
notice() {
msg="${1:-$(cat /dev/stdin)}"
tty -s && echo "${msg}"
[ "${LOGLEVEL}" -ge 5 ] && logger -t bkctld -p daemon.notice "${msg}"
}
warning() {
msg="${1:-$(cat /dev/stdin)}"
tty -s && echo "WARNING : ${msg}" >&2
if [ "${LOGLEVEL}" -ge 4 ]; then
tty -s || echo "WARNING : ${msg}" >&2
logger -t bkctld -p daemon.warning "${msg}"
fi
}
error() {
msg="${1:-$(cat /dev/stdin)}"
tty -s && echo "ERROR : ${msg}" >&2
if [ "${LOGLEVEL}" -ge 5 ]; then
tty -s || echo "ERROR : ${msg}" >&2
logger -t bkctld -p daemon.error "${msg}"
fi
exit 1
}

295
lib/includes Executable file
View file

@ -0,0 +1,295 @@
#!/bin/sh
#
# Config for bkctld
#
[ -f /etc/default/bkctld ] && . /etc/default/bkctld
LIBDIR=${LIBDIR:-/usr/lib/bkctld}
CONFDIR="${CONFDIR:-/etc/evobackup}"
BACKUP_DISK="${BACKUP_DISK:-}"
JAILDIR="${JAILDIR:-/backup/jails}"
INCDIR="${INCDIR:-/backup/incs}"
TPLDIR="${TPLDIR:-/usr/share/bkctld}"
LOCALTPLDIR="${LOCALTPLDIR:-/usr/local/share/bkctld}"
LOCKDIR="${LOCKDIR:-/run/lock/bkctld}"
INDEX_DIR="${INDEX_DIR:-/backup/index}"
IDX_FILE="${IDX_FILE:-${INDEX_DIR}/bkctld-jails.idx}"
SSHD_PID="${SSHD_PID:-/run/sshd.pid}"
SSHD_CONFIG="${SSHD_CONFIG:-/etc/ssh/sshd_config}"
AUTHORIZED_KEYS="${AUTHORIZED_KEYS:-/root/.ssh/authorized_keys}"
FIREWALL_RULES="${FIREWALL_RULES:-}"
LOGLEVEL="${LOGLEVEL:-6}"
CRITICAL="${CRITICAL:-48}"
WARNING="${WARNING:-24}"
DUC=$(command -v duc-nox || command -v duc)
debug() {
msg="${1:-$(cat /dev/stdin)}"
if [ "${LOGLEVEL}" -ge 7 ]; then
echo "${msg}"
logger -t bkctld -p daemon.debug "${msg}"
fi
}
info() {
msg="${1:-$(cat /dev/stdin)}"
if [ "${LOGLEVEL}" -ge 6 ]; then
tty -s && echo "${msg}"
logger -t bkctld -p daemon.info "${msg}"
fi
}
notice() {
msg="${1:-$(cat /dev/stdin)}"
tty -s && echo "${msg}"
[ "${LOGLEVEL}" -ge 5 ] && logger -t bkctld -p daemon.notice "${msg}"
}
warning() {
msg="${1:-$(cat /dev/stdin)}"
tty -s && echo "WARNING : ${msg}" >&2
if [ "${LOGLEVEL}" -ge 4 ]; then
tty -s || echo "WARNING : ${msg}" >&2
logger -t bkctld -p daemon.warning "${msg}"
fi
}
error() {
msg="${1:-$(cat /dev/stdin)}"
tty -s && echo "ERROR : ${msg}" >&2
if [ "${LOGLEVEL}" -ge 5 ]; then
tty -s || echo "ERROR : ${msg}" >&2
logger -t bkctld -p daemon.error "${msg}"
fi
exit 1
}
dry_run() {
test "$DRY_RUN" = "1"
}
current_time() {
date +"%H:%M:%S"
}
# Returns true if the given path is on a btrfs filesystem
is_btrfs() {
path=$1
inode=$(stat --format=%i "${path}")
test $inode -eq 256
}
# Returns the list of all jails
jails_list() {
# shellcheck disable=SC2091
"${LIBDIR}/bkctld-list"
}
# Returns the list of all incs for a jail
incs_list() {
jail_name=${1:?}
# shellcheck disable=SC2091
ls "$(incs_path "${jail_name}")/"
}
# Returns the complete path of a jail
jail_path() {
jail_name=${1:?}
echo "${JAILDIR}/${jail_name}"
}
# Returns the path of incs for a jail
incs_path() {
jail_name=${1:?}
echo "${INCDIR}/${jail_name}"
}
# Returns the path of a specific inc for a jail
inc_path() {
jail_name=${1:?}
inc_name=${2:?}
echo "${INCDIR}/${jail_name}/${inc_name}"
}
jail_config_dir() {
jail_name=${1:?}
echo "${CONFDIR}/${jail_name}.d"
}
jail_incs_policy_file() {
jail_name=${1:?}
jail_config_dir=$(jail_config_dir "${jail_name}")
echo "${jail_config_dir}/incs_policy"
}
current_jail_incs_policy_file() {
jail_name=${1:?}
new_file="$(jail_incs_policy_file "${jail_name}")"
old_file="${CONFDIR}/${jail_name}"
if [ -f "${new_file}" ]; then
echo "${new_file}"
elif [ -f "${old_file}" ]; then
echo "${old_file}"
else
echo ""
fi
}
jail_check_policy_file() {
jail_name=${1:?}
jail_config_dir=$(jail_config_dir "${jail_name}")
echo "${jail_config_dir}/check_policy"
}
current_jail_check_policy_file() {
jail_name=${1:?}
new_file="$(jail_check_policy_file "${jail_name}")"
# old_file="${JAILDIR}/${jail_name}/etc/bkctld-check"
if [ -f "${new_file}" ]; then
echo "${new_file}"
# elif [ -f "${old_file}" ]; then
# echo "${old_file}"
else
echo ""
fi
}
setup_jail_chroot() {
jail_name=${1:?}
jail_path=$(jail_path "${jail_name}")
passwd="${TPLDIR}/passwd"
shadow="${TPLDIR}/shadow"
group="${TPLDIR}/group"
sshrc="${TPLDIR}/sshrc"
[ -f "${LOCALTPLDIR}/passwd" ] && passwd="${LOCALTPLDIR}/passwd"
[ -f "${LOCALTPLDIR}/shadow" ] && shadow="${LOCALTPLDIR}/shadow"
[ -f "${LOCALTPLDIR}/group" ] && group="${LOCALTPLDIR}/group"
[ -f "${LOCALTPLDIR}/sshrc" ] && group="${LOCALTPLDIR}/sshrc"
cd "${jail_path}" || error "${jail_name}: failed to change directory to ${jail_path}."
umask 077
info "1 - Creating the chroot"
rm -rf ./bin ./lib ./lib64 ./run ./usr ./var/run ./etc/ssh/*key
mkdir -p ./dev
mkdir -p ./proc
mkdir -p ./usr/bin
mkdir -p ./usr/sbin
mkdir -p ./usr/lib
mkdir -p ./usr/lib/x86_64-linux-gnu
mkdir -p ./usr/lib/openssh
mkdir -p ./usr/lib64
mkdir -p ./etc/ssh
mkdir -p ./var/log
mkdir -p ./run/sshd
# shellcheck disable=SC2174
mkdir -p ./root/.ssh --mode 0700
# shellcheck disable=SC2174
mkdir -p ./var/backup --mode 0700
ln -s ./usr/bin ./bin
ln -s ./usr/lib ./lib
ln -s ./usr/lib64 ./lib64
ln -s --target-directory=./var ../run
touch ./var/log/lastlog ./var/log/wtmp ./run/utmp
info "2 - Copying essential files"
[ -f /etc/ssh/ssh_host_rsa_key ] && cp /etc/ssh/ssh_host_rsa_key ./etc/ssh
[ -f /etc/ssh/ssh_host_ecdsa_key ] && cp /etc/ssh/ssh_host_ecdsa_key ./etc/ssh
[ -f /etc/ssh/ssh_host_ed25519_key ] && cp /etc/ssh/ssh_host_ed25519_key ./etc/ssh
touch "./${AUTHORIZED_KEYS}"
chmod 600 "./${AUTHORIZED_KEYS}"
cp "${passwd}" ./etc
cp "${shadow}" ./etc
cp "${group}" ./etc
cp "${sshrc}" ./etc/ssh
info "3 - Copying binaries"
cp -f /lib/ld-linux.so.2 ./lib 2>/dev/null || cp -f /lib64/ld-linux-x86-64.so.2 ./lib64
cp /lib/x86_64-linux-gnu/libnss* ./lib/x86_64-linux-gnu
for dbin in /bin/sh /bin/ls /bin/mkdir /bin/cat /bin/rm /bin/sed /usr/bin/rsync /usr/bin/lastlog /usr/bin/touch /usr/sbin/sshd /usr/lib/openssh/sftp-server; do
cp -f "${dbin}" "./${dbin}";
for lib in $(ldd "${dbin}" | grep -Eo "/.*so.[0-9\.]+"); do
cp -p "${lib}" "./${lib}"
done
done
}
setup_jail_config() {
jail_name=${1:?}
jail_path=$(jail_path "${jail_name}")
jail_sshd_config="${jail_path}/${SSHD_CONFIG}"
sshd_config_tpl="${TPLDIR}/sshd_config"
test -f "${LOCALTPLDIR}/sshd_config" && sshd_config_tpl="${LOCALTPLDIR}/sshd_config"
info "4 - Copie default sshd_config"
install -m 0640 "${sshd_config_tpl}" "${jail_sshd_config}"
inctpl="${TPLDIR}/inc.tpl"
test -f "${LOCALTPLDIR}/inc.tpl" && inctpl="${LOCALTPLDIR}/inc.tpl"
info "5 - Copie default inc configuration"
jail_incs_policy_file=$(jail_incs_policy_file "${jail_name}")
mkdir --parents "$(dirname "${jail_incs_policy_file}")"
install -m 0640 "${inctpl}" "${jail_incs_policy_file}"
"${LIBDIR}/bkctld-port" "${jail_name}" auto
}
is_mounted_inside_jail() {
target=${1:?}
# TODO: try to find why it doesn't work with this findmnt(8) command
# findmnt --target "${target}" --tab-file /proc/mounts
grep -q "${target}" /proc/mounts
}
mount_jail_fs() {
jail_name=${1:?}
jail_path=$(jail_path "${jail_name}")
is_mounted_inside_jail "${jail_path}/dev" || mount -nt tmpfs "dev-${jail_name}" "${jail_path}/dev"
[ -e "dev/console" ] || mknod -m 622 "${jail_path}/dev/console" c 5 1
chown root:tty "${jail_path}/dev/console"
[ -e "dev/null" ] || mknod -m 666 "${jail_path}/dev/null" c 1 3
[ -e "dev/zero" ] || mknod -m 666 "${jail_path}/dev/zero" c 1 5
[ -e "dev/ptmx" ] || mknod -m 666 "${jail_path}/dev/ptmx" c 5 2
chown root:tty "${jail_path}/dev/ptmx"
[ -e "dev/tty" ] || mknod -m 666 "${jail_path}/dev/tty" c 5 0
chown root:tty "${jail_path}/dev/tty"
[ -e "dev/random" ] || mknod -m 444 "${jail_path}/dev/random" c 1 8
[ -e "dev/urandom" ] || mknod -m 444 "${jail_path}/dev/urandom" c 1 9
mkdir -p "${jail_path}/dev/pts"
is_mounted_inside_jail "${jail_path}/dev/pts" || mount -t devpts -o gid=4,mode=620 none "${jail_path}/dev/pts"
mkdir -p "${jail_path}/dev/shm"
is_mounted_inside_jail "${jail_path}/dev/shm" || mount -t tmpfs none "${jail_path}/dev/shm"
is_mounted_inside_jail "${jail_path}/proc" || mount -t proc "proc-${jail_name}" "${jail_path}/proc"
ln -fs "${jail_path}/proc/self/fd" "${jail_path}/dev/fd"
ln -fs "${jail_path}/proc/self/fd/0" "${jail_path}/dev/stdin"
ln -fs "${jail_path}/proc/self/fd/1" "${jail_path}/dev/stdout"
ln -fs "${jail_path}/proc/self/fd/2" "${jail_path}/dev/stderr"
ln -fs "${jail_path}/proc/kcore" "${jail_path}/dev/core"
}
read_variable() {
file=${1:?}
var_name=${2:?}
pattern="^\s*${var_name}=-?[0-9]+"
grep --extended-regexp --only-matching "${pattern}" "${file}" | cut -d= -f2
}

View file

@ -1,44 +0,0 @@
#!/bin/sh
passwd="${TPLDIR}/passwd"
shadow="${TPLDIR}/shadow"
group="${TPLDIR}/group"
sshrc="${TPLDIR}/sshrc"
[ -f "${LOCALTPLDIR}/passwd" ] && passwd="${LOCALTPLDIR}/passwd"
[ -f "${LOCALTPLDIR}/shadow" ] && shadow="${LOCALTPLDIR}/shadow"
[ -f "${LOCALTPLDIR}/group" ] && group="${LOCALTPLDIR}/group"
[ -f "${LOCALTPLDIR}/sshrc" ] && group="${LOCALTPLDIR}/sshrc"
umask 077
info "1 - Creating the chroot"
cd "${JAILDIR}/${jail}"
rm -rf bin lib lib64 run usr var/run etc/ssh/*key
mkdir -p dev proc
mkdir -p usr/bin usr/sbin usr/lib usr/lib/x86_64-linux-gnu usr/lib/openssh usr/lib64
mkdir -p etc/ssh var/log run/sshd
mkdir -p root/.ssh var/backup -m 0700
ln -s usr/bin bin
ln -s usr/lib lib
ln -s usr/lib64 lib64
ln -st var ../run
touch var/log/lastlog var/log/wtmp run/utmp
info "2 - Copying essential files"
[ -f /etc/ssh/ssh_host_rsa_key ] && cp /etc/ssh/ssh_host_rsa_key etc/ssh
[ -f /etc/ssh/ssh_host_ecdsa_key ] && cp /etc/ssh/ssh_host_ecdsa_key etc/ssh
[ -f /etc/ssh/ssh_host_ed25519_key ] && cp /etc/ssh/ssh_host_ed25519_key etc/ssh
cp "${passwd}" etc
cp "${shadow}" etc
cp "${group}" etc
cp "${sshrc}" etc/ssh
info "3 - Copying binaries"
cp -f /lib/ld-linux.so.2 lib 2>/dev/null || cp -f /lib64/ld-linux-x86-64.so.2 lib64
cp /lib/x86_64-linux-gnu/libnss* lib/x86_64-linux-gnu
for dbin in /bin/sh /bin/ls /bin/mkdir /bin/cat /bin/rm /bin/sed /usr/bin/rsync /usr/bin/lastlog /usr/bin/touch /usr/sbin/sshd /usr/lib/openssh/sftp-server; do
cp -f "${dbin}" "${JAILDIR}/${jail}/${dbin}";
for lib in $(ldd "${dbin}" | grep -Eo "/.*so.[0-9\.]+"); do
cp -p "${lib}" "${JAILDIR}/${jail}/${lib}"
done
done

167
test/checks.bats Normal file
View file

@ -0,0 +1,167 @@
#!/usr/bin/env bats
# shellcheck disable=SC1089,SC1083,SC2154
load test_helper
@test "Check OK for default values" {
touch "${JAILPATH}/var/log/lastlog"
# With default values (2 days critical, 1 day warning),
# a freshly connected jail should be "ok"
run /usr/lib/bkctld/bkctld-check
assert_equal "0" "$status"
}
@test "Check WARNING for default values" {
lastlog_date=$(date -d -2days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
# With default values (2 days critical, 1 day warning),
# a 2 days old jail should be "warning"
run /usr/lib/bkctld/bkctld-check
assert_equal "1" "$status"
}
@test "Check CRITICAL for default values" {
lastlog_date=$(date -d -3days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
# With default values (2 days critical, 1 day warning),
# a 3 days old jail should be "critical"
run /usr/lib/bkctld/bkctld-check
assert_equal "2" "$status"
}
@test "Check OK for custom values" {
lastlog_date=$(date -d -3days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
CRITICAL=120
WARNING=96
OUT
# With custom values (5 days critical, 4 days warning),
# a 3 days old jail should be "ok"
run /usr/lib/bkctld/bkctld-check
assert_equal "0" "$status"
}
@test "Check WARNING for custom values" {
lastlog_date=$(date -d -3days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
CRITICAL=96
WARNING=48
OUT
# With custom values (4 days critical, 3 days warning),
# a 3 days old jail should be "warning"
run /usr/lib/bkctld/bkctld-check
assert_equal "1" "$status"
}
@test "Check CRITICAL for custom values" {
lastlog_date=$(date -d -10days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
CRITICAL=96
WARNING=48
OUT
# With custom values (4 days critical, 3 days warning),
# a 10 days old jail should be "critical"
run /usr/lib/bkctld/bkctld-check
assert_equal "2" "$status"
}
@test "Check OK for disabled WARNING" {
lastlog_date=$(date -d -2days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
WARNING=0
OUT
# With custom values (warning disabled, default critical),
# a 2 days old jail should still be "ok"
run /usr/lib/bkctld/bkctld-check
assert_equal "0" "$status"
}
@test "Check WARNING for disabled CRITICAL" {
lastlog_date=$(date -d -3days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
CRITICAL=0
OUT
# With custom values (critical disabled, default warning),
# a 3 days old jail should only be "warning"
run /usr/lib/bkctld/bkctld-check
assert_equal "1" "$status"
}
@test "Custom values are parsed with only integers after equal" {
lastlog_date=$(date -d -3days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
CRITICAL=0 # foo
OUT
# With custom values (critical disabled, default warning),
# a 3 days old jail should only be "warning"
run /usr/lib/bkctld/bkctld-check
assert_equal "1" "$status"
}
@test "Commented custom values are ignored" {
lastlog_date=$(date -d -3days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
# CRITICAL=0
OUT
# With commented custom values (critical disabled),
# a 3 days old jail should still be "critical"
run /usr/lib/bkctld/bkctld-check
assert_equal "2" "$status"
}
@test "Invalid custom values are ignored" {
lastlog_date=$(date -d -3days --iso-8601=seconds)
touch --date="${lastlog_date}" "${JAILPATH}/var/log/lastlog"
cat > "/etc/evobackup/${JAILNAME}.d/check_policy" <<OUT
CRITICAL=foo
OUT
# With commented custom values (critical disabled),
# a 3 days old jail should still be "critical"
run /usr/lib/bkctld/bkctld-check
assert_equal "2" "$status"
}
@test "Check WARNING if firewall rules are not sourced" {
firewall_rules_file="/etc/firewall.rc.jails"
set_variable "/etc/default/bkctld" "FIREWALL_RULES" "${firewall_rules_file}"
echo "" > "${firewall_rules_file}"
# Without sourcing
echo "" > "/etc/default/minifirewall"
# … the check should be "critical"
run /usr/lib/bkctld/bkctld-check
assert_equal "1" "$status"
}
@test "Check OK if firewall rules are sourced" {
firewall_rules_file="/etc/firewall.rc.jails"
set_variable "/etc/default/bkctld" "FIREWALL_RULES" "${firewall_rules_file}"
echo "" > "${firewall_rules_file}"
# Sourcing file with '.'
echo ". ${firewall_rules_file}" > "/etc/default/minifirewall"
# … the check should be "critical"
run /usr/lib/bkctld/bkctld-check
assert_equal "0" "$status"
# Sourcing file with 'source'
echo "source ${firewall_rules_file}" > "/etc/default/minifirewall"
# … the check should be "critical"
run /usr/lib/bkctld/bkctld-check
assert_equal "0" "$status"
}

101
test/connectivity.bats Normal file
View file

@ -0,0 +1,101 @@
#!/usr/bin/env bats
# shellcheck disable=SC1089,SC1083,SC2154
load test_helper
@test "Without SSH key" {
run cat "${JAILPATH}/root/.ssh/authorized_keys"
assert_equal "$output" ""
}
@test "With SSH key" {
keyfile=/root/bkctld.key.pub
/usr/lib/bkctld/bkctld-key "${JAILNAME}" "${keyfile}"
# The key should be present in the SSH authorized_keys file
run cat "${JAILPATH}/root/.ssh/authorized_keys"
assert_equal "$output" "$(cat ${keyfile})"
}
@test "Custom port" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-port "${JAILNAME}" "${PORT}"
# A jail should be accessible on the specified SSH port
run nc -vz 127.0.0.1 "${PORT}"
assert_success
}
@test "No IP restriction" {
# A jail has no IP restriction by default in SSH config
run grep "root@0.0.0.0/0" "${JAILPATH}/etc/ssh/sshd_config"
assert_success
}
@test "Single IP restriction" {
# When an IP is added for a jail
/usr/lib/bkctld/bkctld-ip "${JAILNAME}" "10.0.0.1"
# An IP restriction should be present in SSH config
run grep "root@10.0.0.1" "${JAILPATH}/etc/ssh/sshd_config"
assert_success
}
@test "Multiple IP restrictions" {
# When multiple IP are added for a jail
/usr/lib/bkctld/bkctld-ip "${JAILNAME}" "10.0.0.1"
/usr/lib/bkctld/bkctld-ip "${JAILNAME}" "10.0.0.2"
# The corresponding IP restrictions should be present in SSH config
run grep -E -o "root@10.0.0.[0-9]+" "${JAILPATH}/etc/ssh/sshd_config"
assert_line "root@10.0.0.1"
assert_line "root@10.0.0.2"
}
@test "Removing IP restriction" {
# Add an IP
/usr/lib/bkctld/bkctld-ip "${JAILNAME}" "10.0.0.1"
# Remove IP
/usr/lib/bkctld/bkctld-ip "${JAILNAME}" "0.0.0.0/0"
# All IP restrictions should be removed from SSH config
run grep "root@0.0.0.0/0" "${JAILPATH}/etc/ssh/sshd_config"
assert_success
}
@test "Missing AllowUsers" {
# Remove AllowUsers directive in SSH config
sed -i '/^AllowUsers/d' "${JAILPATH}/etc/ssh/sshd_config"
# An error should be raised when trying to add an IP restriction
run /usr/lib/bkctld/bkctld-ip "${JAILNAME}" "10.0.0.1"
assert_failure
}
@test "SSH connectivity" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-port "${JAILNAME}" "${PORT}"
/usr/lib/bkctld/bkctld-key "${JAILNAME}" /root/bkctld.key.pub
ssh_options="-p ${PORT} -i /root/bkctld.key -oStrictHostKeyChecking=no"
# A started jail should be accessible via SSH
run ssh ${ssh_options} root@127.0.0.1 ls
assert_success
/usr/lib/bkctld/bkctld-stop "${JAILNAME}"
# A stopped jail should not be accessible via SSH
run ssh ${ssh_options} root@127.0.0.1 ls
assert_failure
}
@test "Rsync connectivity" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-port "${JAILNAME}" "${PORT}"
/usr/lib/bkctld/bkctld-key "${JAILNAME}" /root/bkctld.key.pub
ssh_options="-p ${PORT} -i /root/bkctld.key -oStrictHostKeyChecking=no"
# A started jail should be accessible via Rsync
run rsync -a -e "ssh ${ssh_options}" /tmp/ root@127.0.0.1:/var/backup/
assert_success
/usr/lib/bkctld/bkctld-stop "${JAILNAME}"
# A stopped jail should not be accessible via Rsync
run rsync -a -e "${ssh_options}" /tmp/ root@127.0.0.1:/var/backup/
assert_failure
}

102
test/incs.bats Normal file
View file

@ -0,0 +1,102 @@
#!/usr/bin/env bats
# shellcheck disable=SC1089,SC1083,SC2154
load test_helper
@test "Inc policy after jail init" {
# An incs_policy file should exist
run test -e "${CONFDIR}/${JAILNAME}.d/incs_policy"
[ "${status}" -eq 0 ]
}
@test "Normal inc creation" {
/usr/lib/bkctld/bkctld-inc
if is_btrfs "/backup"; then
# On a btrfs filesystem, the inc should be a btrfs volume
run is_btrfs "${INCSPATH}/${INC_NAME}"
assert_success
else
# On an ext4 filesystem, the inc should be a regular directory
run test -d "${INCSPATH}/${INC_NAME}"
assert_success
fi
}
@test "Normal inc creation (with old incs policy)" {
mv "${CONFDIR}/${JAILNAME}.d/incs_policy" "${CONFDIR}/${JAILNAME}"
/usr/lib/bkctld/bkctld-inc
if is_btrfs "/backup"; then
# On a btrfs filesystem, the inc should be a btrfs volume
run is_btrfs "${INCSPATH}/${INC_NAME}"
assert_success
else
# On an ext4 filesystem, the inc should be a regular directory
run test -d "${INCSPATH}/${INC_NAME}"
assert_success
fi
}
@test "No inc creation without inc policy" {
# Remove inc_policy
rm -f "${CONFDIR}/${JAILNAME}.d/incs_policy"
# … and old file
rm -f "${CONFDIR}/${JAILNAME}"
/usr/lib/bkctld/bkctld-inc
run test -d "${INCSPATH}/${INC_NAME}"
assert_failure
}
@test "No inc creation with LOCK" {
run rm -rf "${INCSPATH}"
assert_success
touch "/run/lock/bkctld/inc-${JAILNAME}-${INC_NAME}.lock"
/usr/lib/bkctld/bkctld-inc
run test -d "${INCSPATH}/${INC_NAME}"
assert_failure
}
@test "Recent inc is kept after 'rm'" {
# Setup simple incs policy
echo "+%Y-%m-%d.-0day" > "${CONFDIR}/${JAILNAME}.d/incs_policy"
# Prepare an inc older than the policy
recent_inc_path="${INCSPATH}/${INC_NAME}"
# Create the inc, then run 'rm'
/usr/lib/bkctld/bkctld-inc
/usr/lib/bkctld/bkctld-rm
# Recent inc should be present
run test -d "${recent_inc_path}"
assert_success
}
@test "Older inc is removed by 'rm'" {
# Setup simple incs policy
echo "+%Y-%m-%d.-0day" > "${CONFDIR}/${JAILNAME}.d/incs_policy"
# Prepare an inc older than the policy
recent_inc_path="${INCSPATH}/${INC_NAME}"
older_inc_name=$(date -d -1days +"%Y-%m-%d-%H")
older_inc_path="${INCSPATH}/${older_inc_name}"
# Create the inc, rename it to make it older, then run 'rm'
/usr/lib/bkctld/bkctld-inc
mv "${recent_inc_path}" "${older_inc_path}"
/usr/lib/bkctld/bkctld-rm
# Older inc should be removed
run test -d "${older_inc_path}"
assert_failure
}
# TODO: add many tests for incs (creation and removal)

View file

@ -1,133 +1,69 @@
#!/usr/bin/env bats
# shellcheck disable=SC1089,SC1083,SC2154
setup() {
port=$(awk -v min=2222 -v max=2999 'BEGIN{srand(); print int(min+rand()*(max-min+1))}')
date=$(date +"%Y-%m-%d-%H")
inode=$(stat --format=%i /backup)
rm -f /root/bkctld.key* && ssh-keygen -t rsa -N "" -f /root/bkctld.key -q
. /usr/lib/bkctld/config
JAILNAME=$(tr -cd '[:alnum:]' < /dev/urandom | fold -w15 | head -n1)
}
load test_helper
teardown() {
/usr/lib/bkctld/bkctld-remove "${JAILNAME}" && rm -rf "${INCDIR}/*"
}
@test "init" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
inode=$(stat --format=%i /backup)
if [ "${inode}" -eq 256 ]; then
run stat --format=%i "${JAILDIR}/${JAILNAME}"
[ "${output}" -eq 256 ]
@test "Filesystem type" {
if is_btrfs "/backup"; then
# On a btrfs filesystem, the jail should be a btrfs volume
run is_btrfs "${JAILPATH}"
assert_success
else
run test -d "${JAILDIR}/${JAILNAME}"
[ "${status}" -eq 0 ]
# On an ext4 filesystem, the jail should be a regular directory
run test -d "${JAILPATH}"
assert_success
fi
}
@test "start" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
@test "A jail should be able to be started" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
pid=$(cat "${JAILDIR}/${JAILNAME}/${SSHD_PID}")
pid=$(cat "${JAILPATH}/${SSHD_PID}")
# A started jail should have an SSH pid file
run ps --pid "${pid}"
[ "${status}" -eq 0 ]
assert_success
}
@test "stop" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
@test "A jail should be able to be stopped" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
pid=$(cat "${JAILDIR}/${JAILNAME}/${SSHD_PID}")
pid=$(cat "${JAILPATH}/${SSHD_PID}")
/usr/lib/bkctld/bkctld-stop "${JAILNAME}"
# A stopped jail should not have an SSH pid file
run ps --pid "${pid}"
[ "${status}" -ne 0 ]
assert_failure
}
@test "reload" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
@test "A jail should be able to be reloaded" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-reload "${JAILNAME}"
run grep "Received SIGHUP; restarting." "${JAILDIR}/${JAILNAME}/var/log/authlog"
[ "${status}" -eq 0 ]
# A reloaded jail should mention the restart in the authlog
run grep "Received SIGHUP; restarting." "${JAILPATH}/var/log/authlog"
assert_success
}
@test "restart" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
@test "A jail should be able to be restarted" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
bpid=$(cat "${JAILDIR}/${JAILNAME}/${SSHD_PID}")
pid_before=$(cat "${JAILPATH}/${SSHD_PID}")
/usr/lib/bkctld/bkctld-restart "${JAILNAME}"
apid=$(cat "${JAILDIR}/${JAILNAME}/${SSHD_PID}")
[ "${bpid}" -ne "${apid}" ]
pid_after=$(cat "${JAILPATH}/${SSHD_PID}")
# A restarted jail should have a different pid
refute_equal "${pid_before}" "${pid_after}"
}
@test "status" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
@test "Status should return information" {
run /usr/lib/bkctld/bkctld-status "${JAILNAME}"
[ "${status}" -eq 0 ]
assert_success
}
@test "key" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
@test "ON/OFF status can be retrived with 'is-on'" {
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-key "${JAILNAME}" /root/bkctld.key.pub
run cat "/backup/jails/${JAILNAME}/root/.ssh/authorized_keys"
[ "${status}" -eq 0 ]
[ "${output}" = $(cat /root/bkctld.key.pub) ]
}
# A started jail should report to be ON
run /usr/lib/bkctld/bkctld-is-on "${JAILNAME}"
assert_success
@test "port" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-port "${JAILNAME}" "${port}"
run nc -vz 127.0.0.1 "${port}"
[ "${status}" -eq 0 ]
}
@test "inc" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
/usr/lib/bkctld/bkctld-inc
if [ "${inode}" -eq 256 ]; then
run stat --format=%i "${INCDIR}/${JAILNAME}/${date}"
[ "${output}" -eq 256 ]
else
run test -d "${INCDIR}/${JAILNAME}/${date}"
[ "${status}" -eq 0 ]
fi
}
@test "ssh" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-port "${JAILNAME}" "${port}"
/usr/lib/bkctld/bkctld-key "${JAILNAME}" /root/bkctld.key.pub
run ssh -p "${port}" -i /root/bkctld.key -oStrictHostKeyChecking=no root@127.0.0.1 ls
[ "$status" -eq 0 ]
}
@test "rsync" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
/usr/lib/bkctld/bkctld-start "${JAILNAME}"
/usr/lib/bkctld/bkctld-port "${JAILNAME}" "${port}"
/usr/lib/bkctld/bkctld-key "${JAILNAME}" /root/bkctld.key.pub
run rsync -a -e "ssh -p ${port} -i /root/bkctld.key -oStrictHostKeyChecking=no" /tmp/ root@127.0.0.1:/var/backup/
[ "$status" -eq 0 ]
}
@test "check-ok" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
run /usr/lib/bkctld/bkctld-check
[ "$status" -eq 0 ]
}
@test "check-warning" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
touch --date="$(date -d -2days)" "/backup/jails/${JAILNAME}/var/log/lastlog"
run /usr/lib/bkctld/bkctld-check
[ "$status" -eq 1 ]
}
@test "check-critical" {
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
touch --date="$(date -d -3days)" "/backup/jails/${JAILNAME}/var/log/lastlog"
run /usr/lib/bkctld/bkctld-check
[ "$status" -eq 2 ]
/usr/lib/bkctld/bkctld-stop "${JAILNAME}"
# A stopped jail should not report to be ON
run /usr/lib/bkctld/bkctld-is-on "${JAILNAME}"
assert_failure
}

131
test/test_helper.bash Normal file
View file

@ -0,0 +1,131 @@
# shellcheck disable=SC2154 shell=bash
# shellcheck disable=SC2034
setup() {
. /usr/lib/bkctld/includes
rm -f /root/bkctld.key*
ssh-keygen -t rsa -N "" -f /root/bkctld.key -q
set_variable "/etc/default/bkctld" "BACKUP_DISK" "/dev/vdb"
JAILNAME=$(tr -cd '[:alnum:]' < /dev/urandom | fold -w15 | head -n1)
JAILPATH="/backup/jails/${JAILNAME}"
INCSPATH="/backup/incs/${JAILNAME}"
PORT=$(awk -v min=2222 -v max=2999 'BEGIN{srand(); print int(min+rand()*(max-min+1))}')
INC_NAME=$(date +"%Y-%m-%d-%H")
/usr/lib/bkctld/bkctld-init "${JAILNAME}"
}
teardown() {
remove_variable "/etc/default/bkctld" "BACKUP_DISK"
/usr/lib/bkctld/bkctld-remove "${JAILNAME}" && rm -rf "${INCSPATH}"
}
set_variable() {
file=${1:?}
var_name=${2:?}
var_value=${3:-}
if grep -qE "^\s*${var_name}=" "${file}"; then
sed -i "s|^\s*${var_name}=.*|${var_name}=${var_value}|" "${file}"
else
echo "${var_name}=${var_value}" >> "${file}"
fi
}
remove_variable() {
file=${1:?}
var_name=${2:?}
sed -i "s|^\s*${var_name}=.*|d" "${file}"
}
is_btrfs() {
path=$1
inode=$(stat --format=%i "${path}")
test ${inode} -eq 256
}
flunk() {
{ if [ "$#" -eq 0 ]; then cat -
else echo "$@"
fi
} >&2
return 1
}
assert_success() {
if [ "$status" -ne 0 ]; then
flunk "command failed with exit status $status"
elif [ "$#" -gt 0 ]; then
assert_output "$1"
fi
}
assert_failure() {
if [ "$status" -eq 0 ]; then
flunk "expected failed exit status"
elif [ "$#" -gt 0 ]; then
assert_output "$1"
fi
}
assert_equal() {
if [ "$1" != "$2" ]; then
{ echo "expected: $1"
echo "actual: $2"
} | flunk
fi
}
refute_equal() {
if [ "$1" = "$2" ]; then
echo "expected $1 to not be equal to $2" | flunk
fi
}
assert_output() {
local expected
if [ $# -eq 0 ]; then expected="$(cat -)"
else expected="$1"
fi
assert_equal "$expected" "$output"
}
assert_line() {
if [ "$1" -ge 0 ] 2>/dev/null; then
assert_equal "$2" "${lines[$1]}"
else
local line
for line in "${lines[@]}"; do
if [ "$line" = "$1" ]; then return 0; fi
done
flunk "expected line \`$1'"
fi
}
refute_line() {
if [ "$1" -ge 0 ] 2>/dev/null; then
local num_lines="${#lines[@]}"
if [ "$1" -lt "$num_lines" ]; then
flunk "output has $num_lines lines"
fi
else
local line
for line in "${lines[@]}"; do
if [ "$line" = "$1" ]; then
flunk "expected to not find line \`$line'"
fi
done
fi
}
# shellcheck disable=SC2145
assert() {
if ! "$@"; then
flunk "failed: $@"
fi
}