Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

Backup doméstico seguro con Linux, cifrado y ZFS

Última Actualización: 30 de abril de 2013

El objetivo de este artículo es describir uno de mis sistemas de backup (tengo varios). Físicamente reside a varios cientos de kilómetros de mi residencia habitual, así que debe ser algo que no necesite una atención constante, ni una presencia física. En este artículo vamos a ver cómo montar un sistema de backup doméstico con las siguientes características:

  • Funciona sobre Linux virtualizado. La máquina anfitriona es un Windows 8. Se trata de un ordenador empleado de forma rutinaria por una persona ajena a mí. Por supuesto, se puede montar sobre un Linux nativo, claro. De hecho mi idea futura es que esa máquina corra Linux, y ejecute Windows 8 virtualizado, para disfrute de su propietario. Hice un primer intento de ir por esa vía, pero el Secure Boot de la máquina fue un problema y no pude dedicarle más tiempo.

  • Usaremos ZFS así que dispondremos de redundancia, compresión al vuelo, verificación de datos extremo a extremo, snapshots, etc.

  • Los discos duros empleados para el backup están cifrados. La clave de acceso no se almacena en ese ordenador.

  • Uno de los discos duros se almacena en otro lugar.

  • Los discos duros se van alternando, para "gastarlos" todos por igual.

  • Si el disco duro actual empieza a dar errores, podemos conectar la réplica y recuperar la información.

Instalación del Linux y ZFS

En mi caso, la instalación la realizo bajo VirtualBox, en un Windows 8. La máquina tiene un uso ligero, 8 Gigabytes de RAM y un disco duro de un 1 TByte.

Instalo Ubuntu 12.04, por aquello de que tiene soporte hasta 2017. Le doy 50GB de disco duro, todos los cores de la CPU (la máquina tiene un uso ligero) y la mitad de la RAM, 4 Gigabytes.

Una vez instalado, copio allí mi clave pública SSH para poder entrar de forma remota. También instalo OpenVPN como cliente, conectándose a un servidor remoto bajo mi control. Allí le doy una IP privada fija. Esta conexión virtual me permite conectarme al servidor de forma remota aunque la máquina destino esté detrás de un cortafuegos, NAT, etc. Incluso podría hacer cosas como compartir disco por NFS de forma segura.

Procedo a añadir el repositorio del "port" de ZFS a Linux, y lo instalo.

El último paso consiste en hacer que la máquina virtual arranque automáticamente al encender el ordenador. Esto en MS Windows no es trivial, y aún lo es menor porque la máquina virtual debe arrancarse como administrador (se necesitará más adelante). Para ello utilizo VBoxVMService.

Instalación del disco duro para backups y acceso desde la máquina virtual

Compro un disco duro de 2 TeraBytes y lo instalo en la máquina. Se trata de un modelo "green" de WD, lo que me viene muy bien porque se desactiva y consume muy poco cuando no se está utilizando (el backup diario me lleva cosa de una hora, el resto del tiempo el disco está "idle"). Es un modelo algo lento, pero para este contexto no necesito más, y el consumo eléctrico es más prioritario.

Una vez instalado dentro del ordenador, lo arrancamos y vemos que el firmware lo reconoce. Desde Windows 8 también lo vemos sin problemas. No lo formateamos, eso es trabajo del Linux.

Debemos delegar este disco duro COMPLETO al Linux virtualizado. Si tuviésemos un sistema de ficheros Microsoft NTFS en ese disco podríamos modificar los permisos para poder acceder al disco como el usuario sin privilegios de la máquina, pero quiero una delegación completa y por eso necesito arrancar la máquina virtual como administrador.

Desde Windows 8, abrimos un terminal en modo administrador y tecleamos este comando, dentro del directorio de instalación de VirtualBox:

VBoxManage internalcommands createrawvmdk -filename PATH\DiscoUno.vmdk -rawdisk \\.\PhysicalDrive1

Este comando, si tiene éxito, creará un disco duro virtual en VirtualBox. Entramos en el entorno gráfico y asignamos dicho disco duro virtual (que proporciona acceso "raw" al disco duro físico) a nuestra máquina virtual.

Reiniciamos el ordenador, conectamos al Linux virtualizado y vemos, con un "fdisk", que tenemos acceso al disco duro. Perfecto.

Formateo del disco duro de backup, cifrado y creación del "pool" ZFS

Ahora que vemos el disco duro nuevo desde dentro de la máquina virtual, procedemos a formatearlo. Como no es un disco de arranque y no tengo necesidad de ser compatible con sistemas muy antiguos, procedo a realizar un formateado con GPT. Voy a usar todo el disco para ZFS y puedo crear y destruir datasets de forma dinámica, con tamaños también dinámicos, así que creo una única partición usando todo el disco duro:

# parted -a optimal /dev/sdb
GNU Parted 2.3
Using /dev/sdb
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) print
Error: /dev/sdb: unrecognised disk label                                  
(parted) mklabel gpt
(parted) mkpart primary 0% 100%
(parted) print                                    
Model: ATA VBOX HARDDISK (scsi)
Disk /dev/sdb: 2000GB
Sector size (logical/physical): 512B/512B
Partition Table: gpt

Number  Start   End     Size    File system  Name     Flags
 1      1049kB  2000GB  2000GB               primary

(parted) quit
Information: You may need to update /etc/fstab.

Con estos pasos formateamos el disco duro con tabla GPT y una única partición que ocupa todo el disco duro menos el primer megabyte. Ese primer megabyte es espacio reservado para, por ejemplo, un sistema de arranque sofisticado. Ese colchón incluso es útil para hacer mirror ZFS de discos duros de igual capacidad nominal pero cuyo número de sectores no es exactamente idéntico. La partición en sí está alineada, de forma que un disco de 4096 bytes por sector no sufra penalización de rendimiento a la hora de escribir.

El disco está virtualizado, y VirtualBox nos dice que tiene sectores de 512 bytes, cuando nosotros sabemos perfectamente (viendo la hoja de características del fabricante) que sus sectores tienen 4096 bytes. Esto será importante más abajo, cuando creemos el ZPOOL ZFS. De momento vamos verificando que todas las estructuras creadas están alineadas correctamente. Es el caso de esta partición.

Sobre esa partición creamos una estructura LUKS, que es la que nos va a proporcionar cifrado:

# mkfs /dev/ram0
mke2fs 1.42 (29-Nov-2011)
Discarding device blocks: done                            
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
Stride=0 blocks, Stripe width=0 blocks
16384 inodes, 65536 blocks
3276 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=67108864
8 block groups
8192 blocks per group, 8192 fragments per group
2048 inodes per group
Superblock backups stored on blocks: 
        8193, 24577, 40961, 57345

Allocating group tables: done                            
Writing inode tables: done                            
Writing superblocks and filesystem accounting information: done

# mount /dev/ram0 /mnt

(Aquí copiamos un fichero "llave", de un par de kbytes de longitud y generado de forma aleatoria, con buena entropía.
Lo copio de mi portátil al servidor linux virtualizado, y lo copio dentro de "/mnt", que es una RAMDISK, porque no quiero
que este fichero llave acabe en el disco duro)

# cryptsetup luksFormat /dev/sdb1 --key-file=/mnt/llave 

WARNING!
========
This will overwrite data on /dev/sdb1 irrevocably.

Are you sure? (Type uppercase yes): YES

# cryptsetup luksAddKey /dev/sdc1 --key-file=/mnt/llave
Enter new passphrase for key slot:
Verify passphrase:

# cryptsetup luksDump /dev/sdb1
LUKS header information for /dev/sdb1

Version:        1
Cipher name:    aes
Cipher mode:    cbc-essiv:sha256
Hash spec:      sha1
Payload offset: 4096
[...]

# cryptsetup luksOpen --key-file /mnt/llave /dev/sdb1 primerdisco

# zpool create -o ashift=12 datos /dev/mapper/primerdisco

# zpool list
NAME     SIZE  ALLOC   FREE    CAP  DEDUP  HEALTH  ALTROOT
datos   1.81T   504K  1.81T     0%  1.00x  ONLINE  -

# zfs set compression=gzip-9 datos

# zfs set atime=off datos

Creamos una RAMDISK para contener un fichero clave, lo copiamos en ella y creamos un LUKS con dos claves. La primera clave es el fichero "llave", que es lo que usaremos normalmente. La segunda clave es una passphrase con buena entropía y buen tamaño, que guardamos cuidadosamente para situaciones de crisis. No es lo que usaremos normalmente.

Comprobamos que LUKS mantiene el alineamiento de 4096 bytes que necesitamos para un buen rendimiento de escritura.

Por último, activamos el LUKS del disco duro y creamos encima un ZPOOL ZFS. En ese ZPOOL activamos compresión máxima (es un backup, nos compensa y nos lo podemos permitir) y desactivamos "atime", que machaca mucho el disco y realmente no nos aporta nada. Obsérvese que le indicamos un "ashift" explícito de 12 bits, 4096 bytes. El comando debería detectar automáticamente la configuración del disco, pero muchos discos mienten y además tenemos VirtualBox en medio. Mejor ser explícitos y curarnos en salud. En el caso de que el disco duro realmente tuviese sectores de 512 bytes, no pasaría nada, simplemente perderíamos un poco de capacidad.

A continuación creamos un fichero "montar_datos" con un contenido similar a éste:

zpool export datos
cryptsetup luksOpen --key-file /mnt/llave /dev/sdb1 primerdisco && \
zpool import datos && \
echo "OK!!"
vars=`ssh-agent`
eval "$vars"
ssh-add /datos/backups/scripts/id_rsa
dd if=/dev/urandom of=/mnt/llave bs=65536 count=2048
Este script se invoca desde otro script en OTRA máquina, la máquina desde la que solicitamos que se realice un backup. El script en esa otra máquina es:
ssh root@IP "umount /mnt; mkfs /dev/ram0 && mount /dev/ram0 /mnt && chmod 700 /mnt && umount /mnt && mount /dev/ram0 /mnt" \
&& \
scp llave root@IP:/mnt/ \
&& \
ssh root@IP "./montar_datos ; ./z-arranque"

Este script se conecta a la máquina de backup, crea la ramdisk, le cambia los permisos para que solo pueda acceder a ella el administrador (es un poco complicado, con el fin de evitar "race conditions" en los que un atacante gana acceso al directorio ANTES de haberle cambiado los permisos), copia el fichero de claves y luego invoca el script en el backup que monta físicamente del ZFS cifrado y destruye dicha clave de cifrado. El fichero "./z-arranque" son cosas mías, como hacer un "ntpdate" o configurar las rutas de red.

Obsérvese que para entrar en las máquinas remotas para hacerles backup necesito una clave SSH, y que dicha clave se guarda en el propio disco de backup. Que, recordemos, en condiciones normales estará cifrado. Además, me pedirá que introduzca una "passphrase".

Hay un script adicional de desmontaje:

ssh-add -D && \
zpool export datos && \
cryptsetup luksClose /dev/mapper/segundodisco && \
echo "DONE!"

Este script destruye la clave SSH que nos permite entrar en las otras máquinas, desmonta el ZPOOL ZFS y desactiva la clave de acceso LUKS al disco duro. En este modo, nuestros backups están seguros, y es el modo "normal" de esta máquina.

En resumen, la máquina virtual no tiene acceso a los datos de backups, salvo mientras se está haciendo el propio backup.

Pruebas y traspaso de backups

Durante un mes, aproximadamente, estuve transfiriendo 1.2 terabytes de datos de backups e históricos de otro servidor de backup que va a ser eliminado. Este tiempo me permitió evaluar ZFS sobre Linux (yo vengo del mundo Solaris), su combinación con LUKS (cifrado), el impacto de usar sectores de 4096 bytes, etc.

Esta experiencia no estuvo exenta de problemas: incompatibilidad con el "zfs send" de Solaris 10 Update 11, cuelgues esporádicos debido a problemas al recibir un "zfs send" de Solaris en el Linux, etc. Al final lo arreglé sincronizando los snapshots entre ambas máquinas mediante un script muy interesante que será objeto de un artículo futuro.

Una vez que estuve satisfecho del funcionamiento, empecé a hacer los backups diarios de mis sistemas en esta máquina, en vez de en el servidor de backups antiguo, que va a ser eliminado en unos meses. Recordemos que este es uno de mis sistemas de backups, pero tengo varios más.

Añadimos un segundo disco duro, y sincronización

Después de un mes de buen funcionamiento, compré un segundo disco duro, igual al anterior. Mismo modelo. Es necesario que ambos discos duros tengan la misma capacidad exacta para poder hacer "mirror" entre ellos. Si la diferencia es de unos pocos sectores, se puede aprovechar el megabyte extra que dejamos al formatear con GPT para igualar el tamaño de la partición.

En general, meter dos discos iguales tiene ventajas e inconvenientes. La ventaja más evidente es que puedo hacer un "mirror" sin problemas, porque son trivialmente idénticos en capacidad. Otra ventaja es que cualquier asimetría en el rendimiento de los discos es una buena indicación de problemas. La desventaja fundamental es que comprando los dos discos a la vez y siendo el mismo modelo, es más probable que fallen con poca diferencia de tiempo. No queremos que los dos discos del "mirror" fallen con un mes de diferencia, ¿verdad?.

En este caso compré el segundo disco con un mes de diferencia, y no van a sufrir el mismo uso, así que preferí comprar el mismo modelo por sencillez y porque estoy muy contento con su muy bajo consumo eléctrico cuando no se utiliza.

Una vez instalado el segundo disco, procedemos a hacer las mismas operaciones: crear un dispositivo virtual en el host Windows 8, mapearlo a la máquina virtual, particionarlo, y montar LUKS encima.

Modificamos los scripts de montaje y desmontaje del backup para que abran y cierren ambos discos.

Pero en vez de crear un ZPOOL ZFS en el paso final, lo añadimos al ZPOOL ya existente, como "mirror". Hay que tener mucho cuidado de añadirlo como "mirror" (RAID 1) y no como "stripping" (RAID 0), porque si lo hacemos así tendremos un ZPOOL de 4 terabytes pero sin redundancia:

# zpool attach datos primerdisco segundodisco

# zpool status
  pool: datos
 state: ONLINE
status: One or more devices is currently being resilvered.  The pool will
        continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
  scan: resilver in progress since Thu Apr 11 20:21:27 2013
    646G scanned out of 1.21T at 35.4M/s, 4h43m to go
    646G resilvered, 52.30% done
config:

        NAME              STATE     READ WRITE CKSUM
        datos             ONLINE       0     0     0
          mirror-0        ONLINE       0     0     0
            primerdisco   ONLINE       0     0     0
            segundodisco  ONLINE       0     0     0  (resilvering)

errors: No known data errors

La sincronización de los discos empieza de forma automática.

La máquina opera con normalidad y podemos seguir usándola. Incluso podemos apagarla, y seguirá sincronizándose cuando la encendamos de nuevo.

Podemos ver la actividad detallada de los discos duros. La copia de un disco al otro es muy evidente:

# zpool iostat -v 10 9999
[...]
                     capacity     operations    bandwidth
pool              alloc   free   read  write   read  write
----------------  -----  -----  -----  -----  -----  -----
datos             1.21T   621G    364     27  43.7M  79.4K
  mirror          1.21T   621G    364     27  43.7M  79.4K
    primerdisco       -      -    361     15  43.7M  96.4K
    segundodisco      -      -      0    378      0  43.8M
----------------  -----  -----  -----  -----  -----  -----
[...]

Una vez que los discos están sincronizados, seguimos usando el sistema unas semanas para probar bastante bien el disco duro nuevo.

Archivado offline y remoto del segundo disco duro

Tenemos un disco duro con una antigüedad de un mes y otro de dos meses. Voy a dejar enchufado el disco duro que tiene un mes. El disco duro de dos meses se desenchufa y se guarda en casa de un amigo. Recordemos que está cifrado con LUKS.

Antes de desconectar el disco duro, hacemos un "scrub" para asegurarnos de que ambos discos duros están en perfectas condiciones y los datos almacenados son consistentes. Si hay cualquier tipo de problemas, ZFS nos los dirá. De hecho lo solucionará si es posible, aprovechando la redundancia que aún tenemos.

Ahora desactivamos el disco duro que vamos a retirar:

# zpool offline datos segundodisco

# zpool status
  pool: datos
 state: DEGRADED
status: One or more devices has been taken offline by the administrator.
        Sufficient replicas exist for the pool to continue functioning in a
        degraded state.
action: Online the device using 'zpool online' or replace the device with
        'zpool replace'.
  scan: resilvered 1.21T in 24h51m with 0 errors on Fri Apr 12 21:12:41 2013
config:

        NAME              STATE     READ WRITE CKSUM
        datos             DEGRADED     0     0     0
          mirror-0        DEGRADED     0     0     0
            primerdisco   ONLINE       0     0     0
            segundodisco  OFFLINE      0     0     0

errors: No known data errors

Ahora hacemos un ciclo de backup normal, solo sobre un disco duro.

Cuando terminamos, reactivamos el disco duro para ver cómo se sincronizan. Esto es básicamente lo que haremos dentro de unos meses, sincronizar los cambios del disco duro más actualizado al disco duro que teníamos archivado offline.

# zpool online datos segundodisco

# zpool status
  pool: datos
 state: ONLINE
  scan: resilvered 498M in 0h1m with 0 errors on Thu Apr 18 14:10:29 2013
config:

        NAME              STATE     READ WRITE CKSUM
        datos             ONLINE       0     0     0
          mirror-0        ONLINE       0     0     0
            primerdisco   ONLINE       0     0     0
            segundodisco  ONLINE       0     0     0

errors: No known data errors

Vemos que se sincroniza lo justo, y que lo hace rápido.

Volvemos a poner el disco duro "offline", lo desenchufamos y se lo entregamos a mi amigo para su custodia.

# zpool offline datos segundodisco

# zpool status
  pool: datos
 state: DEGRADED
status: One or more devices has been taken offline by the administrator.
	Sufficient replicas exist for the pool to continue functioning in a
	degraded state.
action: Online the device using 'zpool online' or replace the device with
	'zpool replace'.
  scan: resilvered 148K in 0h0m with 0 errors on Sat Apr 20 17:35:32 2013
config:

	NAME                      STATE     READ WRITE CKSUM
	datos                     DEGRADED     0     0     0
	  mirror-0                DEGRADED     0     0     0
	    primerdisco           ONLINE       0     0     0
	    10256769708510827902  OFFLINE      0     0     0  was /dev/mapper/segundodisco

errors: No known data errors


Historia

  • 30/abr/13: Primera versión de esta página.



Python Zope ©2013 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS