ZFS on LUKS

De Área31 Hackerspace
Membro do hackerspace feliz por ter um mirror ZFS (semelhante ao RAID-1) criptografadinho com LUKS, com cache em NVME e tudoooo, chega a ser poético de tão bonito.
Autor: 
* Coffnix

"Home Network Defense"

Utilize criptografia pra tudo

Motivo

Pelo visto você é um bom menino (ou menine kekekeke), que criptografou seu disco certinho, e seguiu as boas práticas recomendadas pelo área31 hackerspace lendo os artigos HackForge e aprendeu até a criar seu próprio storage criptografado em roteador residencial. Aprendeu inclusive a desbloquear seu LUKS durante o boot usando um device USB. Logo, agora vc quer um espelho de discos semelhante ao RAID-1, só que usando ZFS on Linux. Então se divirta :D

É fundamental reconhecer que, embora indesejado, o acesso não autorizado aos seus arquivos é uma possibilidade real. Muitas pessoas acreditam na inviolabilidade de seus dados, confiando nas promessas de segurança feitas por empresas, ou na suposta eficácia de seus sistemas operacionais e softwares de proteção. No entanto, é um equívoco comum subestimar que, com a motivação correta e acesso físico, qualquer zé mané da esquina com conhecimentos básicos, ou em um cenário mais extremo, entidades governamentais (sim, agentes do ESTADO), podem acessar ou comprometer seus dados. Portanto, a implementação de criptografia em nível de sistema de arquivos em seus sistemas não é apenas recomendada, mas essencial. A mensagem aqui é clara: não negligencie a segurança dos seus dados. Priorize a criptografia abrangente para garantir sua proteção, ou seja, CRIPTOGRAFE TUDO, estúpido!


Requisitos

Aqui utilizamos OpenSUSE + ZFS on Linux, mas você tem liberdade de utilizar em qualquer distro, adapte somente os comandos e arquivos caso utilize outra distro.

Procedimento

Instale o ZFS

Configure o repositório "Filesystem Tools" criando o arquivo abaixo:

   /etc/zypp/repos.d/repo-filesystems.repo
[filesystems]
name=Filesystem tools and FUSE-related packages (openSUSE_Tumbleweed)
enabled=1
autorefresh=1
baseurl=https://download.opensuse.org/repositories/filesystems/openSUSE_Tumbleweed/
type=rpm-md
gpgcheck=1
gpgkey=https://download.opensuse.org/repositories/filesystems/openSUSE_Tumbleweed/repodata/repomd.xml.key

Agora atualize o cache e instale os pacotes necessários:

root # zypper refresh
root # zypper in zfs-kmp-default zfs-ueficert zfs

Adicione o ZFS ao boot:

root # systemctl enable zfs-import-cache
root # systemctl enable zfs-mount

Configure o LUKS

Agora configure crie o luks em cada disco. Lembre-se de alterar os ID dos devices de "ata-ST8000NE001-2M7101_WSD0SC28" e "ata-ST8000NE001-2M7101_WSD0SC30" para os seus:

   Atenção:

Há um bug em aberto no ZFS, eu caí nele inclusive, logo por enquanto use --type luks1 ao invés de luks2. Mais infos: https://github.com/openzfs/zfs/issues/14533

root # mkdir -p /etc/area31/headerstore
root # cryptsetup luksFormat --force-password --type luks1 --cipher aes-xts-plain64 --hash sha512 --key-size 512 /dev/disk/by-id/ata-ST8000NE001-2M7101_WSD0SC28 --align-payload 8192 --header /etc/area31/headerstore/ata-ST8000NE001-2M7101_WSD0SC28.img
root # cryptsetup luksFormat --force-password --type luks1 --cipher aes-xts-plain64 --hash sha512 --key-size 512 /dev/disk/by-id/ata-ST8000NE001-2M7101_WSD0SC30 --align-payload 8192 --header /etc/area31/headerstore/ata-ST8000NE001-2M7101_WSD0SC30.img

Crie uma chave para desbloqueio do LUKS:

root # mkdir -p /opt/area31/keystore/keyfile ; dd if=/dev/urandom of=/opt/area31/keystore/keyfile bs=4096 count=1
root # chmod 400 /opt/area31/keystore/keyfile

Inclua a chave em ambos os discos:

root # cryptsetup luksAddKey /dev/disk/by-id/ata-ST8000NE001-2M7101_WSD0SC30 --header /etc/area31/headerstore/ata-ST8000NE001-2M7101_WSD0SC30.img /opt/area31/keystore/keyfile
root # cryptsetup luksAddKey /dev/disk/by-id/ata-ST8000NE001-2M7101_WSD0SC28 --header /etc/area31/headerstore/ata-ST8000NE001-2M7101_WSD0SC28.img /opt/area31/keystore/keyfile

Monte usando a chave:

root # cryptsetup luksOpen /dev/disk/by-id/ata -ST8000NE001-2M7101_WSD0SC30 --header /etc/area31/headerstore/ata-ST8000NE001-2M7101_WSD0SC30.img --key-file=/opt/area31/keystore/keyfile storage_disk1_luks
root # cryptsetup luksOpen  /dev/disk/by-id/ata-ST8000NE001-2M7101_WSD0SC28 --header /etc/area31/headerstore/ata-ST8000NE001-2M7101_WSD0SC28.img --key-file=/opt/area31/keystore/keyfile storage_disk2_luks

Configure o mirror ZFS

Crie um novo pool ZFS como mirror com 2 dispositivos LUKS montados previamente, com nome vdisk0 montado em /storage:

root # mkdir -p /storage ; chattr +i /storage
root # zpool create -f -o ashift=12 -m /storage vdisk0 mirror /dev/mapper/storage_disk1_luks /dev/mapper/storage_disk2_luks

Inicie o serviço ZFS para montar o pool sem precisar reiniciar:

root # systemctl start zfs-mount
root # systemctl start zfs-import-cache

Para desmontar /storage (se necessário):

root # zpool export vdisk0

Para remontar o /storage (se necessário):

root # zpool import vdisk0

Habilite o 'relatime' no pool ZFS para otimizar o acesso a tempos de leitura:

root # zfs set relatime=on vdisk0

Ativar a compressão lz4 para economizar espaço no pool ZFS:

root # zfs set compression=lz4 vdisk0

Ative a deduplication (IMPORTANTE!!!):

root # zfs set dedup=on vdisk0

Verifique se dedup está ativo:

root # zfs get dedup vdisk0

Para checar o status do ZFS:

root # zfs get all vdisk0
root # zpool status

Liste todos os datasets ZFS:

root # zfs list -o name,mountpoint,used,avail,refer

Ative cache em SSD ou NVME

Adicione um NVME ou SSD como cache ao pool ZFS. Basta criar uma partição comum do tipo 8e (Linux) entre 50GB e 100GB e depois adiciona-lo. Ex:

root # zpool add vdisk0 cache /dev/nvme0n1p7


Montagem automática do ZFS

Crie o config para o script:

root # mkdir -p /opt/area31/conf ; touch /opt/area31/conf/mount-luks-zfs.conf

Com o seguinte conteúdo:

   /opt/area31/bin/mount-luks-zfs.sh
DISK_ID_01="ata-ST8000NE001-2M7101_WSD0SC30"
DISK_ID_02="ata-ST8000NE001-2M7101_WSD0SC28"
LUKS_DISK_01="storage_disk1_luks"
LUKS_DISK_02="storage_disk2_luks"
LUKS_KEYFILE="/etc/area31/keystore/keyfile"
DIR_LUKS_HEADER="/etc/area31/headerstore"
DIR_ZFS="/storage"
ZFS_VDISK_01="vdisk0"

Agora crie o script para montagem automática do ZFS:

root # mkdir -p /opt/area31/bin ; touch /opt/area31/bin/mount-luks-zfs.sh ; chmod +x /opt/area31/bin/mount-luks-zfs.sh

Com o seguinte conteúdo:

   /opt/area31/bin/mount-luks-zfs.sh (bash source code)
#!/bin/bash

###########################################################################
# Verifica se o script já está em execução

SCRIPT_NAME="${0##*/}"

if [ "$(pgrep -cx "${SCRIPT_NAME}")" -gt 1 ]; then
    echo "O script já está em execução, aguarde 10 segundos para tentar novamente."
    sleep 10

    if [ "$(pgrep -cx "${SCRIPT_NAME}")" -gt 1 ]; then
        echo "O script já está em execução e não será executado novamente."
        exit 1
    fi
fi

###########################################################################
# Iniciando script

CNF="/opt/area31/conf/mount-luks-zfs.conf"

# Checa se existe o arquivo de CONF, caso não exista ele sai
if [ -f "${CNF}" ] && [ ! -z "${CNF}" ]; then
    source ${CNF}
else
    echo "${CNF} não encontrado."
    exit 1
fi

ERROR="0"

# Lista de variáveis para verificar
variables=("DIR_ZFS" "ZFS_VDISK_01" "DIR_LUKS_HEADER" "LUKS_KEYFILE" "LUKS_DISK_02" "LUKS_DISK_01" "DISK_ID_02" "DISK_ID_01")

# Função para verificar variável
check_variable() {
    local var_name="$1"

    # Usando indireção para pegar o valor da variável
    local actual_value="${!var_name}"

    # Verificar se a variável está definida e não está vazia
    if [[ -z "$actual_value" ]]; then
        echo "Erro: $var_name não está definido ou está vazio!"
        return 1
    fi
}

# Verificar todas as variáveis do CONF antes de iniciar o script
for var in "${variables[@]}"; do
    check_variable "$var" | |  exit 1
done

###########################################################################################################

check_pool_zfs_active(){
	CHECK_MOUNTED="$(mount | grep zfs | grep  "${DIR_ZFS}" | wc -l)"
	if [ "${CHECK_MOUNTED}" -ne 0 ]; then
		CHECK_NONE_ZFS="$(zpool status | grep 'no pools available' | wc -l 2> /dev/null)"
		if [ "${CHECK_NONE_ZFS}" -ne 0 ]; then
			ZFS_UMOUNTED="1"
		else
			ZFS_UMOUNTED="0"
		fi
	else
		echo -e "/storage desmontado e ZFS inativo"
		exit 0
	fi
}

umount_luks(){
	CHECK_LUKS_DISK_01="$(lsblk -o NAME,TYPE,MOUNTPOINT | grep -w 'crypt'  | grep ${LUKS_DISK_01} | wc -l)"
	CHECK_LUKS_DISK_02="$(lsblk -o NAME,TYPE,MOUNTPOINT | grep -w 'crypt'  | grep ${LUKS_DISK_02} | wc -l)"

	if [ "${CHECK_LUKS_DISK_01}" -ne 0 ]; then
		cryptsetup luksClose "${LUKS_DISK_01}"
		if [ "$?" -ne 0 ]; then
			echo -e "Erro ao desmontar o LUKS \"${LUKS_DISK_01}\""
			ERROR="1"
		else
			echo -e "Sucesso ao desmontar o LUKS \"${LUKS_DISK_01}\""
		fi
	else
		echo "LUKS \"${LUKS_DISK_01}\" desmontado."
	fi

	if [ "${CHECK_LUKS_DISK_02}" -ne 0 ]; then
		cryptsetup luksClose "${LUKS_DISK_02}"
		if [ "$?" -ne 0 ]; then
			echo -e "Erro ao tentar desmontar o LUKS \"${LUKS_DISK_02}\""
			ERROR="1"
		else
			echo -e "Sucesso ao desmontar o LUKS \"${LUKS_DISK_02}\""
		fi
	else
		echo "LUKS \"${LUKS_DISK_02}\" desmontado."
	fi
}


mount_zfs(){
	# Checa se o disco está montado
	CHECK_LUKS_DISK_01="$(lsblk -o NAME,TYPE,MOUNTPOINT | grep -w 'crypt'  | grep ${LUKS_DISK_01} | wc -l)"
	CHECK_LUKS_DISK_02="$(lsblk -o NAME,TYPE,MOUNTPOINT | grep -w 'crypt'  | grep ${LUKS_DISK_02} | wc -l)"

	# monte usando a chave do pendrive
	if [ "${CHECK_LUKS_DISK_01}" -eq 0 ]; then
		echo -e "Tentando montar o LUKS \"${LUKS_DISK_01}\" do dispositivo \"${DISK_ID_01}\"..."
		cryptsetup luksOpen "/dev/disk/by-id/${DISK_ID_01}" --header "${DIR_LUKS_HEADER}/${DISK_ID_01}.img" --key-file="${LUKS_KEYFILE}" "${LUKS_DISK_01}"
		if [ "$?" -ne 0 ]; then
			ERROR="1"
			exit 1
		else
			echo -e "Sucesso ao montar os dispositivos LUKS do dispositivo \"${DISK_ID_01}\"."
		fi
	fi

	if [ "${CHECK_LUKS_DISK_02}" -eq 0 ]; then
		echo -e "Tentando montar o LUKS \"${LUKS_DISK_02}\" do dispositivo \"${DISK_ID_02}\"..."
		cryptsetup luksOpen "/dev/disk/by-id/${DISK_ID_02}" --header "${DIR_LUKS_HEADER}/${DISK_ID_02}.img" --key-file="${LUKS_KEYFILE}" "${LUKS_DISK_02}"
		if [ "$?" -ne 0 ]; then
			ERROR="1"
			exit 1
		else
			echo -e "Sucesso ao montar os dispositivos LUKS do dispositivo \"${DISK_ID_02}\"."
		fi
	fi

	zfs get all "${ZFS_VDISK_01}" &> /dev/null
	if [ "$?" -eq 0 ]; then
		echo -e "ZFS inicializado. Caso deseje utilize $0 check ou $0 umount"
	else
		if [ "${ERROR}" -eq 0 ]; then
			echo -e "LUKS montado. Tentando inicializar o ZFS..."
			zpool import "${ZFS_VDISK_01}"
			if [ "$?" -ne 0 ]; then
				ERROR="1"
				exit 1
			else
				echo -e "Sucesso ao inicializar o ZFS.\n$(df -hT "${DIR_ZFS}")"
			fi
		fi
	fi
}


case "$1" in
	check)
		zfs get all "${ZFS_VDISK_01}"
		zpool status "${ZFS_VDISK_01}"
	;;
	umount)
		CHECK_MOUNTED="$(mount | grep zfs | grep "${DIR_ZFS}" | wc -l)"
		if [ "${CHECK_MOUNTED}" -eq 0 ]; then
			echo -e "ZFS desmontado. Tentando desmontar o LUKS..."
			umount_luks
		else
			zpool export "${ZFS_VDISK_01}"
			if [ "$?" -ne 0 ]; then
				echo -e "Erro ao tentar desmontar o ZFS \"${ZFS_VDISK_01}\""
			else
				umount_luks
			fi
		fi
	;;
	*)
		mount_zfs
	;;
esac


Configure o systemd

Caso queira que o ZFS seja montado automaticamente no processo de boot, crie o seguinte arquivo:

   /etc/systemd/system/mount-luks-zfs.service
[Unit]
Description=mount-luks-zfs
Wants=networking.service
After=networking.service

[Service]
Type=simple
RemainAfterExit=yes
ExecStart=/opt/area31/bin/mount-luks-zfs.sh
ExecStop=/opt/area31/bin/mount-luks-zfs.sh umount
ExecReload=/opt/area31/bin/mount-luks-zfs.sh
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Ative e inicie:

root # systemctl enable /etc/systemd/system/mount-luks-zfs.service
root # systemctl start mount-luks-zfs.service

Seja feliz! :D

Cookies nos ajudam a entregar nossos serviços. Ao usar nossos serviços, você concorda com o uso de cookies.