Skip to content

Commit

Permalink
First version, with hooks support
Browse files Browse the repository at this point in the history
  • Loading branch information
blackandred committed Oct 27, 2018
1 parent e131e06 commit 27b5ecc
Show file tree
Hide file tree
Showing 15 changed files with 298 additions and 99 deletions.
137 changes: 110 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,122 @@ Secure Storage
Encrypts the data with a TrueCrypt AES 256 hidden volume, and exposes a HTTP endpoint for having a possibility
to enter the passphrase when the server will go down.

Protect your server against hosting providers. Even if they would mount your storage it will be encrypted.
Its much more difficult to get into your data when its encrypted, but REMEMBER, it's not impossible!

```bash
ansible-galaxy install blackandred.server_secure_storage
```

Mounting and unmounting from shell
----------------------------------

To mount/unmount a volume from shell there are prepared easy to use scripts.

```bash
# please replace "storage" with the name you placed in "enc_mount_name" variable (see configuration reference)

# mounting
/usr/local/bin/tcmount-storage.sh 'your-secret-here'

# unmounting
/usr/local/bin/tcunmount-storage.sh
```

Mounting by a HTTP call
-----------------------

You can mount the storage using an HTTP call, so also you can easily automate the process using some healthchecks.

```bash
curl -v http://your-host:8015/deploy/volume_mount?enc_token=YOUR-PASSWORD-THERE&token=YOUR-DEPLOYER-TOKEN-HERE
```

Legend:
- enc_token: Its a volume password or secret password (depends on which volume you want to mount)
- token: Thin-Deployer token, configurable in `deployer_token` (see: configuration reference)

Notes:
- IT IS HIGHLY RECOMMENDED TO HIDE DEPLOYER SERVICE BEHIND A SSL GATEWAY

Configuration reference
-----------------------

```yamlex
enc_file: /.do-not-delete # path, where all of the data will be stored
enc_file_size: 400M # examples: 256M, 20G, 500G
enc_mount_name: storage # mount name, should be a-z, lower case, without special letters
enc_file_filesystem: ext4 # any filesystem supported by mkfs (and supported by the operating system)
enc_filesystem_create_if_not_exists: true
# passwords, change them
enc_passphrase: "test123"
enc_hidden_volume_passphrase: "hidden123"
enc_hidden_volume_size: "390M"
# tcplay settings
hashing_algorithm: whirlpool
encryption_algorithm: AES-256-XTS
# Mounting webhook
# ================
# Allows to expose a HTTP endpoint, so you could
# invoke that endpoint to put the passphrase to mount the volume
# eg. after server crash. So the password will not be stored on the server
# and how you will secure it is your concern.
#
deployer_token: "" # set a token to enable
slack_or_mattermost_webhook_url: "" # put a slack/mattermost webhook URL to enable notifications
systemd_service_name: "volume-deployer"
deployer_listen: "0.0.0.0"
deployer_listen_port: "8015"
roles:
- role: blackandred.server_secure_storage
tags: decrypt
vars:
enc_file: /.do-not-delete # path, where all of the data will be stored
enc_file_size: 10000M # examples: 256M, 20G, 500G
enc_mount_name: storage # mount name, should be a-z, lower case, without special letters
enc_file_filesystem: ext4 # any filesystem supported by mkfs (and supported by the operating system)
enc_filesystem_create_if_not_exists: true
# passwords, change them, NOTE: You can keep them secure in an Ansible Vault
# by default the hidden volume is mounted during deployment time
# but normally you can choose over the HTTP endpoint or via SHELL which volume you want to mount
# by choosing one of defined passwords just
enc_passphrase: "test123"
enc_hidden_volume_passphrase: "hidden123"
enc_hidden_volume_size: "9950M"
# tcplay settings
hashing_algorithm: whirlpool
encryption_algorithm: AES-256-XTS
# Mounting webhook
# ================
# Allows to expose a HTTP endpoint, so you could
# invoke that endpoint to put the passphrase to mount the volume
# eg. after server crash. So the password will not be stored on the server
# and how you will secure it is your concern.
#
deployer_token: "" # set a token to enable
slack_or_mattermost_webhook_url: "" # put a slack/mattermost webhook URL to enable notifications
systemd_service_name: "volume-deployer"
deployer_listen: "0.0.0.0"
deployer_listen_port: "8015"
```

Hooks PRE/POST
--------------

Before encryption (detaching the volume) you can execute your code to eg. shutdown services,
and after decryption you can bring them up back.

Example:

```yamlex
hook_pre_mount: ""
hook_post_mount: >
set -x;
mkdir -p /mnt/storage/project /mnt/storage/docker /project /var/lib/docker;
mount -o bind /mnt/storage/project /project || exit 1;
mount -o bind /mnt/storage/docker /var/lib/docker || exit 1;
mount --bind /var/lib/docker/plugins /var/lib/docker/plugins || true;
mount --make-private /var/lib/docker/plugins || true;
if [[ -f /etc/systemd/system/project.service ]]; then
sudo systemctl restart docker;
sleep 5;
sudo systemctl restart project;
fi;
hook_pre_unmount: >
if [[ -f /etc/systemd/system/project.service ]]; then
sudo systemctl disable docker;
sudo systemctl disable project;
sudo systemctl stop project;
sudo systemctl stop docker;
fi;
umount /var/lib/docker/plugins || true;
umount /project || true;
umount /var/lib/docker || true;
hook_post_unmount: ""
```
18 changes: 18 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
enc_file: /.do-not-delete # path, where all of the data will be stored
enc_file_size: 400M # examples: 256M, 20G, 500G
enc_mount_name: storage # mount name, should be a-z, lower case, without special letters
enc_mount_path: /mnt/storage
enc_file_filesystem: ext4 # any filesystem supported by mkfs (and supported by the operating system)
enc_filesystem_create_if_not_exists: true
hide_sensitive_output: true

# passwords, change them
enc_passphrase: "test123"
Expand All @@ -25,3 +27,19 @@ slack_or_mattermost_webhook_url: "" # put a slack/mattermost webhook URL to ena
systemd_service_name: "volume-deployer"
deployer_listen: "0.0.0.0"
deployer_listen_port: "8015"

#
# Hooks
# =====
# Allows to execute actions before/after mounting and unmounting
#
hook_pre_mount: >
echo 'Mounting'
hook_post_mount: ""

hook_pre_unmount: >
echo 'Unmounting'
hook_post_unmount: >
echo 'Unmounted'
22 changes: 22 additions & 0 deletions tasks/create-deploy-hooks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
- name: Create a directory for hooks
become: yes
file:
path: "/usr/local/server-secure-storage/{{ enc_mount_name }}/hooks.d/{{ item }}"
state: directory
with_items:
- pre_mount
- post_mount
- pre_unmount
- post_unmount

- name: Adding hooks
become: yes
template:
src: "usr/local/server-secure-storage/hooks.d/{{ item }}/001_default.sh"
dest: "/usr/local/server-secure-storage/{{ enc_mount_name }}/hooks.d/{{ item }}/001_default.sh"
mode: +x
with_items:
- pre_mount
- post_mount
- pre_unmount
- post_unmount
8 changes: 5 additions & 3 deletions tasks/create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
become: yes
shell: "losetup '{{ loopback.stdout }}' '{{ enc_file }}'"

- name: Create an encrypted volume
- name: Create an encrypted volume, this may take a while, you can observe /tmp/create.log
become: yes
no_log: True
shell: "time /usr/local/bin/tc-create-volume.sh '-d /dev/{{ loopback.stdout }} -g -a {{ hashing_algorithm }} -b {{ encryption_algorithm }}' '{{ enc_passphrase }}' '{{ enc_hidden_volume_passphrase }}' '{{ enc_hidden_volume_size }}'"
no_log: "{{ hide_sensitive_output }}"
shell: "time /usr/local/bin/tc-create-volume.sh '-d /dev/{{ loopback.stdout }} -g -a {{ hashing_algorithm }} -b {{ encryption_algorithm }}' '{{ enc_mount_name }}' '{{ enc_passphrase }}' '{{ enc_hidden_volume_passphrase }}' '{{ enc_hidden_volume_size }}'"
vars:
ansible_command_timeout: 21600
8 changes: 8 additions & 0 deletions tasks/generate-scripts.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
- name: Copy shared bash code
become: yes
template:
src: "./usr/local/lib/server-secure-storage.sh"
dest: "/usr/local/lib/server-secure-storage.sh"
mode: +x

- name: Generate scripts
become: yes
template:
Expand All @@ -8,3 +15,4 @@
- { src: 'tc-create-volume.sh', dest: 'tc-create-volume.sh' }
- { src: 'tc-mount-volume.sh', dest: 'tc-mount-volume.sh' }
- { src: 'tcmount-xxx.sh.j2', dest: "tcmount-{{ enc_mount_name }}.sh" }
- { src: 'tcunmount-xxx.sh.j2', dest: "tcunmount-{{ enc_mount_name }}.sh" }
3 changes: 3 additions & 0 deletions tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
- name: Generate scripts
include: generate-scripts.yml

- name: Create deploy hooks
include: create-deploy-hooks.yml

- name: Create a new volume if it was not created yet
include: create.yml
when: enc_filesystem_create_if_not_exists == True and storage_file.stat.exists == False
Expand Down
12 changes: 1 addition & 11 deletions tasks/mount.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
- name: Mount a volume first time with the secret passphrase (with filesystem formatting)
become: yes
no_log: True
shell: "/usr/local/bin/tcmount-{{ enc_mount_name }}.sh '{{ enc_hidden_volume_passphrase }}' --format"
vars:
ansible_ssh_pipelining: no
when: storage_file.stat.exists == False

- name: Mount a volume usually with the secret passphrase
become: yes
no_log: True
no_log: "{{ hide_sensitive_output }}"
shell: "/usr/local/bin/tcmount-{{ enc_mount_name }}.sh '{{ enc_hidden_volume_passphrase }}'"
when: storage_file.stat.exists == True

19 changes: 15 additions & 4 deletions templates/usr/local/bin/tc-create-volume.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
set -x

TCPLAY_PARAMS=$1
PASSPHRASE=$2
HIDDEN_VOLUME_PASSPHRASE=$3
HIDDEN_VOLUME_SIZE=$4
MOUNT_NAME=$2
PASSPHRASE=$3
HIDDEN_VOLUME_PASSPHRASE=$4
HIDDEN_VOLUME_SIZE=$5

expect -c "
spawn tcplay -c ${TCPLAY_PARAMS};
Expand All @@ -30,4 +31,14 @@ expect -c "
send y\r;
interact;
"
" > /tmp/create.log

cat /tmp/create.log

if [[ $(cat /tmp/create.log) != *"Writing volume headers to disk..."* ]]; then
echo " .. Failed to create a volume"
exit 1
fi

/usr/local/bin/tcmount-${MOUNT_NAME}.sh ${PASSPHRASE} --format
exit $?
58 changes: 4 additions & 54 deletions templates/usr/local/bin/tcmount-xxx.sh.j2
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

source /usr/local/lib/server-secure-storage.sh

#
# This file is automatically generated by Ansible
# All changes made manually to this file will be LOST!
Expand All @@ -9,55 +11,12 @@
MAPPED_DEVICE_PATH=/dev/mapper/{{ enc_mount_name }}
LOOPBACK_CACHE_PATH=/tmp/{{ enc_mount_name }}
ENC_FILE={{ enc_file }}
MOUNT_PATH=/mnt/{{ enc_mount_name }}
MOUNT_PATH={{ enc_mount_path }}
MOUNT_NAME={{ enc_mount_name }}
MOUNT_FS={{ enc_file_filesystem }}
HOOKS_DIR=/usr/local/server-secure-storage/{{ enc_mount_name }}/hooks.d/
### END OF GENERATED BY ANSIBLE

reformat () {
mkfs.${MOUNT_FS} ${MAPPED_DEVICE_PATH}
}

setup_loopback () {
# at first deactivate all previously assigned loopback devices
for prev_loopback in $(losetup -a |grep "${ENC_FILE}" | cut -d":" -f1); do
losetup -d ${prev_loopback}
done

loopback_device=$(losetup -f)
losetup ${loopback_device} ${ENC_FILE} > /dev/null
echo ${loopback_device}
}

mount_mapped_volume () {
mkdir -p ${MOUNT_PATH}
mount ${MAPPED_DEVICE_PATH} ${MOUNT_PATH}/
}

umount_previously_mounted_volume () {
if [[ -d ${MOUNT_PATH} ]]; then
umount ${MOUNT_PATH} 2> /dev/null
fi

if [[ -e ${MAPPED_DEVICE_PATH} ]]; then
echo " .. Closing the previously opened device"
cryptsetup close ${MAPPED_DEVICE_PATH}
fi
}

decrypt () {
passphrase=$1
loopback_device=$(setup_loopback)

tc-mount-volume.sh "${MOUNT_NAME} -d ${loopback_device}" ${passphrase}
sleep 1

if [[ ! -e ${MAPPED_DEVICE_PATH} ]]; then
echo " .. Cannot decrypt volume"
exit 1
fi
}

main () {
echo " >> Mounting ${MOUNT_NAME}"

Expand All @@ -77,13 +36,4 @@ main () {
verify_and_exit
}

verify_and_exit () {
if mount | grep ${MOUNT_PATH} > /dev/null; then
exit 0
fi

echo " .. Cannot find ${MOUNT_PATH} in the list of active mount points"
exit 1
}

main "$@"
24 changes: 24 additions & 0 deletions templates/usr/local/bin/tcunmount-xxx.sh.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

source /usr/local/lib/server-secure-storage.sh

#
# This file is automatically generated by Ansible
# All changes made manually to this file will be LOST!
#

### GENERATED BY ANSIBLE
MAPPED_DEVICE_PATH=/dev/mapper/{{ enc_mount_name }}
LOOPBACK_CACHE_PATH=/tmp/{{ enc_mount_name }}
ENC_FILE={{ enc_file }}
MOUNT_PATH={{ enc_mount_path }}
MOUNT_NAME={{ enc_mount_name }}
MOUNT_FS={{ enc_file_filesystem }}
HOOKS_DIR=/usr/local/server-secure-storage/{{ enc_mount_name }}/hooks.d/
### END OF GENERATED BY ANSIBLE

main () {
exec_hooks "pre_unmount"
umount_previously_mounted_volume
exec_hooks "pre_unmount"
}
Loading

0 comments on commit 27b5ecc

Please sign in to comment.