Páginas

Mostrando entradas con la etiqueta Github. Mostrar todas las entradas
Mostrando entradas con la etiqueta Github. Mostrar todas las entradas

28 septiembre, 2019

Backups: Sincronizar datos locales a un bucket de Amazon S3 usando AWSCLI en PowerShell y Bash

Hace tiempo que vengo usando este método de realizar mis backups personales, aunque siendo correctos, por definición no se trata realmente del concepto de "Backups" sino de un sistema de sincronización. Si usando el mismo método almacenara copias durante un periodo de tiempo determinado con una autoeliminación de los backups más viejos, es decir una política de retención donde haiga la posibilidad de disponer de puntos de restauración dentro de las fechas establecidas en la política de retención, así como la posibilidad de almacenar un histórico de copias en frio en fechas más viejas. En ese caso si estaríamos aplicando el concepto de backup.

Esta es una forma sincronizar datos en dos sitios o ubicaciones distintas. Por un lado tenemos los datos locales que queremos salvaguardar y por otro un sitio cloud donde realizaremos la sincronización de dichos datos en un momento programado y definido previamente para que se realice después de manera automática.

Se trata de scripts de PowerShell y Bash Shell Script, compatibles en entornos Windows, Linux y MacOS, para sincronizar datos locales a un bucket S3 (Simple Storage Service) de Amazon Web Services a través de la interfaz de línea de comandos de AWSCLI (serverless). Donde se realiza un push de datos locales a una ubicación remota en un bucket de almacenamiento de Amazon S3.

En mí repositorio personal de Gihub comento en más detalle el procedimiento a seguir e iré actualizándolo con mejoras cuando proceda.
PowerShell
  • Funciones específicas para montar y desmontar unidades externas USB donde se almacenarán las copias de Veeam Backup.
  • Realizar compresiones 7zip cifrada de forma simétrica, usando adicionalmente un método de capas de ficheros comprimidos para almacenar la BBDD + key file de KeePassXC.
  • Sincronizar con AWS CLI los datos locales con el objeto (carpeta/directorio) del bucket S3.
  • Generar un fichero log de todo el proceso.
  • Enviar el fichero de log vía Email.
  • Enviar el fichero de log, contenido en formato de mensaje o ambas vía ChatBot de Telegram.
Bash Shell Script
  • Generar un fichero log de todo el proceso.
  • Sincronizar los datos locales con el objeto (carpeta/directorio) del bucket S3 usando AWSCLI.
  • Enviar el fichero de log vía Email desde el smtp de una cuenta de correo Gmail configurado en SSMTP.
Saludos!

26 agosto, 2019

Compartir recursos con Samba sin usuario y sin password entre Linux y Windows

Para poder compartir recursos Samba desde Linux a Windows sin usar ningún login usuario/password de modo que sea un acceso invitado, debemos configurar una serie de directivas en el fichero de configuración de Samba.

Instalación de Samba (en el caso de distribuciones basadas en Debian).
sudo apt install samba samba-client smbfs
Creamos la carpeta que vamos a compartir. Un usuario externo que tiene acceso al equipo a través de Samba, el sistema le da como nombre de usuario nobody y como nombre de grupo nogroup, por lo que hacemos propietarios de esta carpeta a los usuarios externos.
sudo mkdir /mnt/publica
sudo chmod 777 /mnt/publica
sudo chown nobody:nogroup /mnt/publica
Editamos el fichero de configuración de Samba.
/etc/samba/smb.conf
Añadimos o modificamos las siguientes directivas. Workgroup será el nombre del grupo de trabajo (en caso de no formar parte de un entorno de dominio) y permitimos el acceso a invitados.
[global]
   workgroup = WORKGROUP
   usershare allow guests = yes
Establecemos el recurso compartido.
[NombreRecursoCompartido]
   comment = Mi recurso compartido
   path = /mnt/publica
   browseable = Yes
   writeable = Yes
   public = yes

security = SHARE
  • browseable: Atravesar y navegar entre las subcarpetas del recurso compartido.
  • writeable: Escribir en el recurso compartido.
  • public: el sinónimo de "guest ok", permite el ver y acceder al recurso comapartido de manera pública.
  • security: Por defecto suele estar comentado como ";   security = user", permite que se pueda acceder sin establecer ningún nombre de usuario.
La directiva "security" es la que realmente permita un acceso tipo invitado desde cualquier otro sistema que no sea Linux ya se Windows o MacOS.

Debe colocarse sin espacios y al final del fichero de configuración smb.conf, un salto de línea después del último recurso compartido.

Reiniciamos el servidor Samba para aplicar los cambios.
sudo systemctl restart samba
Hago referencia a uno de mis repositorios sobre un caso práctico de este ejemplo.

Riesgo de seguridad

No se recomienda habilitar la directiva "usershare allow guests" por motivos de seguridad, la configuración por defecto estará deshabilitada con valor "No". 

10 mayo, 2019

RaspberryPi: Captar temperatura y humedad con sensor DHT22 (AM2302) y enviar datos a ThingSpeak.com

RaspberryPi: Captar temperatura y humedad con sensor DHT22 (AM2302) y enviar datos a ThingSpeak.com.

Repositorio Github: https://github.com/adrianlois/RaspberryPi-Projects/tree/master/01.rpi-sensor-dht22-am2302-thingspeak

Requisitos previos

Instalar Python 2

sudo apt update -y
sudo apt install python-pip -y
sudo python -m pip install --upgrade pip setuptools wheel

Instalar librerías Adafruit

git clone https://github.com/adafruit/Adafruit_Python_DHT.git
cd Adafruit_Python_DHT
sudo python setup.py install

Esquema de conexión: Temperatura y Humedad sensor DHT22 AM2302

El sensor DHT22 AM2302 consta de 3 pines:
  • + (positivo) = Voltaje 3V (Power - pin 1)
  • - (negativo) = Tierra (Ground - pin 6)
  • out = Salida de datos (GPIO4 - pin 7)
Podemos hacer otro esquema de conexión siempre que se respete la funcionalidad de cada pin del sensor al esquema de conexión de la RaspberryPi. Hay que tener en cuenta el número de GPIO donde iría conectado el pin "out" del sensor, este nos proporciona la salida de datos captados.

En el script "thingspeak_raspi_dht22.py" establecemos el número de GPIO en la variable raspiNumGPIO.

Test de conexión del sensor DHT22 AM2302

Teniendo las librerías Adafruit ya instaladas una forma de probar la conexión entre el sensor y la RaspberryPi es ejecutar el script de ejemplo para obtener los datos de temperatura y humedad actuales.

Se le pasa como primer parámetro el modelo de sensor "2302" y el número de GPIO del pin correspondiente donde está conectado la salida de datos (out) del sensor, en este caso 4 (GPIO4).
$ python Adafruit_Python_DHT/examples/AdafruitDHT.py 2302 4
Temperatura=21.2* Humedad=57.7%
Figura 1: Esquema de conexión GPIO Raspberry PI 3 B/B+.
Figura 2: Conexión física del sesnor dht22 am2302 a Raspberry Pi 3 B+.

Script Python para el envío de datos a la cuenta de ThingSpeak (thingspeak_raspi_dht22.py)

import sys
import urllib2
import RPi.GPIO as GPIO
import Adafruit_DHT
# Write API Key ThingSpeak.com
miWriteAPIKey = "XXXXXXXXXXXXXXXX"
# Numero GPIO de conexión out del sensor dht22 a RaspberryPi
raspiNumGPIO = "X"
def getSensorData():
   RH, T = Adafruit_DHT.read_retry(Adafruit_DHT.DHT22, raspiNumGPIO)
   return (str(RH), str(T))
def main():
   print 'Iniciando...'
   baseURL = 'https://api.thingspeak.com/update?api_key=%s' % miWriteAPIKey
   while True:
       try:
           RH, T = getSensorData()
           f = urllib2.urlopen(baseURL + "&field1=%s&field2=%s" % (RH, T))
           print f.read()
           f.close()
           sleep(5)
       except:
           print 'Terminado.'
           break
if __name__ == '__main__':
main()

Configuración de la cuenta de ThingSpeak.com

  1. Registrarse en https://thingspeak.com
  2. Si no tenemos cuenta previa en MathWorks. ThingSpeak nos redirigue, con la posibilidad de usar el mismo email, hacia el registro de https://www.mathworks.com.
  3. Crear un nuevo channel en nuestra perfil y agregar dos field chart (Temperatura y Humedad).
  4. Obtener el "Write API Key" del channel creado.
  5. Establecer el "Write API Key" en el script "thingspeak_raspi_dht22.py" en la variable miWriteAPIKey.
  6. Podemos usar y personalizar plantillas de código Matlab para la visualización de los datos registrados en los field chart del channel de ThingSpeak.

Programar el envío de datos a ThingSpeak.com (crontab)

Añadimos una tarea programada en cron (/etc/crontab) que ejecutará el script "thingspeak_raspi_dht22.py" enviando los datos captados a nuestra cuenta de ThingSpeak.
@hourly root python /thingspeak/thingspeak_raspi_dht22.py
Channel ThingSpeak: https://thingspeak.com/channels/769908/

Figura 3: Ejemplo de tablas de campo (field chart) del envío de datos a la cuenta de ThingSpeak.

Saludos!

30 enero, 2019

Túnel SSH Port Forwarding: Local, Remote y Dynamic [Explicado]

¿Qué es SSH?

ssh-tunnelingSSH (Secure Shell) es un protocolo de comunicaciones seguras entre dos sistemas usando una arquitectura cliente/servidor que permite a los usuarios conectarse a un host remotamente para su posterior administración. A diferencia de otros protocolos de comunicación remota como FTP o Telnet (Telecommunication Network), SSH cifra la sesión de conexión y la comunicación ofreciendo también una infraestructura de autenticación PKI (Public Key Infrastructure) para la conexión con el host remoto.

Para poder establecer una conexión a un host que tenga implementado el rol de servidor SSH es necesario disponer de un cliente SSH. El puerto por defecto de escucha del servicio SSH es el 22.

Una vez se disponga de una comunicación establecida al servidor SSH se pueden usar los protocolos SFTP (SSH File Transfer Protocol) o SCP (Secure Copy) para la transferencia segura de ficheros entre cliente/servidor.

¿Qué es un tunel SSH? (SSH Tunneling)

Es una técnica que consiste en encapsular un protocolo de red sobre otro o un determinado tráfico de red. En el caso de usar conexión SSH se trata de realizar esta técnica de forma segura añadiendo una capa de seguridad en la que los datos viajan de forma cifrada a través del túnel SSH, realizando un reenvío o redirección de puertos (port forwarding) desde una máquina local a otra remota (o viceversa) para establecer una comunicación a un recurso o servicio accesible solamente desde uno de los extremos de la red.

Suele usarse para conectarse a un servicio remoto que solo se tiene acceso desde la red local remota. A través de un cliente SSH nos podemos conectar a un servidor SSH remoto que tenga acceso a la red remota a la que queremos acceder, especificar un reenvío de puertos para establecer las conexiones a ese servicio y así poder ejecutarlo de forma local desde la máquina que inició la conexión SSH. El método más habitual suele ser el reenvío de puertos local.

¿Tipos de reenvíos de puertos? (Port forwarding)


SSH Local Port Forwarding (LPF)

Local (Redirección de puertos local): Reenvía un puerto local a un host remoto.

Si un servicio se ejecuta en la máquina remota en la que establecemos la conexión y queremos ejecutar este servicio en la máquina local que inicia la conexión, se puede acceder a este servicio de desde nuestra máquina local usando la dirección local de lookback (127.0.0.1 o localhost) haciendo referencia al puerto mapeado a través de la conexión de inicio de la sesión SSH.

Pongamos un escenario de ejemplo de una conexión local port forwarding de escritorio remoto RDP realizando pivoting a otro equipo dinstinto al servidor SSH.

Supongamos que estamos en una red A y queremos acceder por escritorio remoto a un equipo situado en una red B (192.168.0.10), pero que por restricción del Firewall de la red A solo podemos realizar solicitudes de conexión al puerto 22 de SSH y no al puerto 3389 del servicio RDP. Lo que podemos hacer es crear una conexión desde un cliente SSH desde la red A (172.16.0.20) a un servidor SSH situado en la red B (192.168.0.15), en esa conexión tendremos que especificar un port forwarding local en un puerto aleatorio (mayor del puerto 1024 para que esté fuera del rango de "puertos bien conocidos") por ejemplo el 9090 como puerto origen (puerto local) de la red A y la IP:3389 del equipo de la red B al que nos queremos conectar (192.168.0.10), después de establecer esta parametrización nos conectamos al servidor SSH de la red B para crear el túnel local.

En el host de la red A que estableció la conexión desde el cliente SSH (172.16.0.20) abrimos un cliente de RDP (mstsc Windows, rdesktop o Remmina en Linux) y nos conectamos a "localhost:9090". Esto redirigirá la petición del host localhost (172.16.0.20) del puerto 9090 al servidor SSH de la red B que a su vez este redirigirá la petición al host destino 192.168.0.10 hacia el puerto 3389 y así establecer la conexión RDP.

En el caso de querer conectarse a otro servicio o desde otro host que no sea el propio host que realiza conexión desde el cliente SSH se especificaría: "<host-remoto>:<puerto-remoto>".

Desde una terminal se usa el parámtro -L para port forwarding local.
ssh -L <puerto-local-escucha>:<host-remoto>:<puerto-remoto> <servidor-ssh>
ssh -L 9090:192.168.0.10:3389 mi.servidorssh.com

Si el servidor remoto SSH al que nos conectamos para realizar el pivoting del primer salto (mi.servidorssh.com) está expuesto por otro puerto que no sea el 22 por defecto, podemos especificarlo con el parámetro "-p <puerto-servidor-ssh>".

PuTTY - Local port forwarding SSH Tunneling.
Figura 1: PuTTY - Local port forwarding SSH Tunneling.
  • Cliente SSH (Red A): 172.16.0.20
  • Servidor SSH (Red B): 192.168.0.15
  • Host RDP (Red B): 192.168.0.10
Figura 2: SSH port forwarding - Túnel SSH local.

SSH Remote Port Forwarding (RPF)

Remote (Reedirección de puertos inverso): Reenvía un puerto remoto a un host local.

Al contrario que una conexión de túnel local, si un servicio solo es accesible desde una máquina de la red local y se necesita tener conexión a el de forma remota. Se puede usar un puerto específico de escucha para conectarse a esa máquina remota y que esta nos proporcione el servicio a través de ella, pivotando posteriormente esa conexión a la máquina desde la que se inicia la sesión SSH.

Pongamos un ejemplo con los mismos hosts del escenario anterior de una conexión remote port forwarding de escritorio remoto RDP.

Supongamos que estamos en una red A y queremos ofrecer el servicio de escritorio remoto RDP situado en la red A (172.16.0.20), pero este servicio hacía este host solo solamente es accesible desde la red A y no desde otras redes externas. Lo que podemos hacer es crear una conexión con un cliente SSH desde el mismo host de la red A a un servidor SSH situado en la red B (192.168.0.15), en esa conexión tendremos que especificar un port forwarding remote en un puerto aleatorio (mayor del puerto 1024 para que esté fuera del rango de "puertos bien conocidos") por ejemplo el 9090 como puerto origen (puerto remoto) de la red B y la 172.16.0.20:3389 del equipo de la red A del que queremos ofrecer la conexión RDP (sería válido también localhost:3389 ya que se trata del mismo equipo local quien es el cliente SSH que establece la conexión), después de establecer esta parametrización nos conectamos al servidor SSH de la red B para crear el túnel remoto.

La idea es prácticamente la misma que el tunel local, la diferencia está en que la conexión se establece de forma inversa (reverse port forwarding)

En el host 192.168.0.10 de la red B abrimos un cliente de RDP (mstsc Windows, rdesktop o Remmina en Linux) y nos conectamos a "192.168.0.5:9090" (servidor SSH de la red A). Esto redirigirá la petición al puerto 9090 al host destino de la red B 172.16.0.20 hacia el puerto 3389 para establecer la conexión RDP.

En el caso de ofrecer otro servicio o desde otro host que no sea el propio host que realiza conexión desde el cliente SSH se especificaría: "<host-local>:<puerto-local>".

Directivas en el servidor SSH (/etc/ssh/sshd_config)

Para que el servidor SSH permita el conexiones de puertos de reenvío remoto es necesario habilitar principalmente la directiva GatewayPorts. Referencia: https://linux.die.net/man/5/sshd_config

AllowTcpForwarding

Especifica si se permite el reenvío de TCP, de forma predeterminada el valor está establecido a "yes".

GatewayPorts

Especifica si los hosts remotos pueden conectarse a los puertos reenviados para el cliente. De forma predeterminada, enlaza los reenvíos de puertos remotos a la dirección de loopback. Esto evita que otros hosts remotos se conecten a puertos reenviados. Por lo que para poder usarse para el reenvío de puertos remotos debe establecerse a "yes".

Desde una terminal se usa el parámtro -R para port forwarding remote.
ssh -R <puerto-remoto-escucha>:<host-local>:<puerto-local> <servidor-ssh>
ssh -R 9090:172.168.0.20:3389 mi.servidorssh.com
o también
ssh -R 9090:localhost:3389 mi.servidorssh.com

Si el servidor remoto SSH al que nos conectamos para realizar el pivoting del primer salto (mi.servidorssh.com) está expuesto por otro puerto que no sea el 22 por defecto, podemos especificarlo con el parámetro "-p <puerto-servidor-ssh>"

PuTTY - Remote port forwarding SSH Tunneling.
Figura 3: PuTTY - Remote port forwarding SSH Tunneling.
  • Cliente SSH y Host RDP (Red A): 172.16.0.20
  • Servidor SSH (Red B): 192.168.0.15
  • Client RDP (Red B): 192.168.0.10
Figura 4: SSH port forwarding - Túnel SSH remoto.

SSH Dynamic Port Forwarding (DPF)

Dynamic (Redirección de puertos dínamico): Utiliza SOCKS.

Se comporta como un proxy SOCKS, suele usarse si nos necesitamos conectar a un software que espera un reenvío de SOCKS.

La idea es igual que cuando se establece un túnel local SSH. La diferencia es que los datos se enviarían a todos los destinos remotos. Para utilizar este tipo de conexión es necesario que la aplicación del cliente que se conecta al puerto local debe enviar tráfico mediante el protoclo SOCKS. En el lado del túnel del cliente se crearía un porxy SOCKS y la aplicación (por ejemplo un navegador web) utiliza el protocolo SOCKS para especificar donde debe enviarse el tráfico cuando sale del otro extremo del túnel SSH.

SSH creará un proxy SOCKS que escuchará las conexiones en el puerto 9090, al recibir una solicitud enrutará el tráfico a través del canal SSH establecido entre la red A (172.16.0.20) y la red B (192.168.0.15). Para ello, es necesario configurar la aplicación (navegador web) del host de la red A para que apunte al proxy SOCKS en el puerto 9090 en localhost. De ese modo estaremos saliendo con la conexión pública (si el mecanismo NAT está habilitado) a través de este navegador web como si estuviésemos en la red B.

Si en vez usar la dirección de loopback 127.0.0.1 refiriéndose a la dirección local, se usa una dirección de red no enrutable como 0.0.0.0 (indicando desde "cualquier lugar" -anywhere-) y especificando un puerto concreto, podemos indicar que se configure un Proxy SOCKS en el navegador web de cualquier máquina remota y así desde cualquier máquina de la red se establezca un túnel directo a nuestra máquina y que este tunelice el tráfico web recibido, bypaseando el tráfico restringido por los firewalls internos de la empresa.

Ejecutándolo desde un cliente SSH desde la red A. Desde una terminal se usa el parámtro -D para port forwarding dynamic.
ssh -D <puerto-origen-dinámico-escucha> <servidor-ssh>
ssh -D 9090 mi.servidorssh.com

Si el servidor remoto SSH al que nos conectamos para realizar el pivoting del primer salto (mi.servidorssh.com) está expuesto por otro puerto que no sea el 22 por defecto, podemos especificarlo con el parámetro "-p <puerto-servidor-ssh>"

PuTTY - Dynamic port forwarding SSH Tunneling.
Figura 5: PuTTY - Dynamic port forwarding SSH Tunneling.

Proxy SOCKS navegador web.
Figura 6: Proxy SOCKS navegador web.
  • Cliente SSH (Red A): 172.16.0.20
  • Servidor SSH (Red B): 192.168.0.15
SSH port forwarding - Túnel SSH dinámico.
Figura 7: SSH port forwarding - Túnel SSH dinámico.

Repositorio Github: https://github.com/adrianlois/SSH-Tunnel-Port-forwarding

Saludos!

18 enero, 2019

Cifrar passwords con PowerShell

Con PowerShell podemos generar o almacenar contraseñas simétricas, secretos (tipo API Key o Access Token) o simplemente cadenas de texto y guardarlas cifradas de forma segura en ficheros de scripts Powershell.

En un caso práctico este método lo llevo utilizando ya un tiempo para almacenar las contraseñas necesarias que se usan en scripts automatizados para realizar en este caso copias de seguridad.

Crear passwords cifradas con PowerShell para usar desde el mismo usuario

Cuando establecemos una password en Powershell y la volcamos en un fichero, el fichero que contiene la password cifrada solo sería se podría usar desde el mismo usuario que la estableció.

Para establecer una password con Powershell y volcarla a un fichero de forma cifrada convirtiendo la cadena de texto claro establecida a una cadena cifrada segura (ConvertFrom-SecureString).
Read-Host -AsSecureString "Password fichero" | ConvertFrom-SecureString | Set-Content "C:\Passwords\fichero-passwd-cifrada"
Llamar al fichero con la password cifrada almacenada en un script Powershell, convertir la cadena cifrada en texto claro (ConvertTo-SecureString).
Get-Content "C:\Passwords\fichero-passwd-cifrada" | ConvertTo-SecureString
Repositorio donde se muestra este método y un video demo PoC.

Crear passwords cifradas con PowerShell generando un keyfile portable para usar desde cualquier usuario o máquina

Otra forma de cifrar la passwords en Powershell sería generar un keyfile (fichero llave único para descifrarlas). De ese modo teniendo el keyfile y referenciándolo en el script de Powershell se pueden cifrar con el keyfile y posteriormente descifrar las password establecida volcada en el fichero.

La ventaja respecto al método anterior, es que se pueden descifrar los ficheros que almacenan las passwords cifradas desde cualquier usuario de la misma máquina o cualquier usuario de otra máquina distinta. Pudiendo así transportar los ficheros de passwords y keyfile para ser utilizados de una forma portable por otros usuarios o máquinas.

Generar el keyfile y establecer la password para volcarla a un fichero asociándola al keyfile (Read-Host -AsSecureString , ConvertFrom-SecureString -Key).
$CifradoKeyFile = New-Object Byte[] 32
[Security.Cryptography.RNGCryptoServiceProvider]::Create().GetBytes($CifradoKeyFile)
$CifradoKeyFile | Out-File "C:\Passwords\CifradoKeyFile.key"

Read-Host -AsSecureString "Establece una Password" | ConvertFrom-SecureString -Key (Get-Content "C:\Passwords\CifradoKeyFile.key") | Set-Content "C:\Passwords\fichero-passwd-cifrada"
Para descifrar y leer el contenido cifrado a través de un script Powershell se concatena el fichero de password cifrado y su keyfile (ConvertTo-SecureString -Key).
Get-Content "C:\Passwords\fichero-passwd-cifrada" | ConvertTo-SecureString -Key (Get-Content "C:\Passwords\CifradoKeyFile.key")

¿Cómo parametrizar usuario y password en un script Powershell sin establecer la password en texto claro?

Haciendo uso de lo anterior, usando un fichero key para que la password pueda ser leída desde cualquier usuario o máquina, un ejemplo podría ser el siguiente.
$password = Get-Content C:\Passwords\password.txt | ConvertTo-SecureString -Key (Get-Content C:\Passwords\aes.key)
$credential = New-Object System.Management.Automation.PsCredential("Username",$password)
Repositorio donde se muestra este método con generación de keyfile y un video demo PoC.

ConverTo-SecureString hace uso de DPAPI (Data Protection API) usando un algoritmo de cifrado AES256 en versiones modernas o 3DES en antiguas, crea un hash en base a la clave del usuario. Más info de como funciona el proceso de cifrado.

Liberar o eliminar las passwords almacenadas en un puntero de memoria

Cuando se crea una cadena tipo SecureString esta es almacenada en un espacio de memoria a parte, donde el recolector de basura de .NET de Powershell (CG - Garbage Collector) no puede acceder por lo que es recomendable vaciar este espacio de memoria que se asigna a un puntero.

ZeroFreeCoTaskMemUnicode: Libera un puntero a una cadena no administrada que se ha asignado con el método.

En el siguiente ejemplo se muestra como se crea una cadena segura y se almacenada en un variable. Se convierte a texto claro gracias a que conocemos el indicador del puntero en memoria y finalmente liberamos ese puntero. Con esto evitará el almacenamiento en memoria de la credencial almacenada. 
PS C:\> $sec = Read-Host -AsSecureString
*******
PS C:\> $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
PS C:\> $ptr
114228428
PS C:\> $plaintext = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
PS C:\> $plaintext
Abc123.
PS C:\> [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($ptr)
Saludos!

11 diciembre, 2018

Docker Cheat Sheet: Comandos Docker [Guía de referencia]

Llevo una temporada trabajando con el sistema de contenedores de Docker, poco a poco fuí recopilando los comandos de Docker que iba utilizando a través de su web oficial.

Cree un repositorio para irlo actualizando paulatinamente con la referencia de estos comandos de Docker.
En la medida de lo posible iré actualizando esta entrada con el nuevo contenido que vaya incorporando al repositorio de Github.

Instalación Docker en Ubuntu 18.04

Instalación Docker

Actualizar los repositorios existentes.
sudo apt update
Instalar paquetes previos que permitan a apt usar paquetes a través de HTTPS.
sudo apt install apt-transport-https ca-certificates curl software-properties-common
Agregar la clave GPG del repositorio oficial de Docker.
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
Agregar el repositorio de Docker en las fuentes de APT.
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
Finalmente, se instala Docker.
sudo apt install docker-ce
Se comprueba que el daemon de Docker está iniciado y el proceso está habilita para iniciarse en el arranque.
sudo systemctl status docker
Si no estuviese iniciado y/o habilitado para el arranque del sistema.
sudo systemctl start docker
sudo systemctl enable docker

Ejecutar el comando Docker sin sudo o desde otro usuario

De forma predeterminada el comando Docker solo puede ser ejecutado por el usuario root o por un usuario del grupo docker. Donde “username” sería el nombre del usuario.
sudo usermod -aG docker username
Para comprobar que el usuario forma parte del grupo docker se pude ejecutar.
id -nG

Referencia de comandos Docker - Docker Cheat Sheet

https://docs.docker.com/engine/reference/commandline/docker

Comandos gestión Docker

Listar contenedores en ejecución. Listar todos los contenedores en ejecución y parados (-a indica todos).
docker ps –a
Listar imágenes descargadas en local (-a muestra todas las imágenes incluidas las intermedias).
docker images
Descargar imagen a caché local desde un repositorio público.
docker pull ubuntu:18.04
Loguearse en Docker Hub, será necesario estar autenticado para subir (push) una imagen.
docker login
docker login -u <usuario> -p <password>
Subir una imagen a un repositorio (Docker Hub).
docker push <imagen_local>
Crear una imagen de un contenedor personalizado en ejecución.
docker commit <ID o nombre_contenedor> <nombre_imagen_nueva>
Para subir una imagen creada a un repositorio como puede ser Docker Hub, es neecsario "tagearla" antes.
docker tag <imagen_local> <nombre_tag_imagen>
docker push <nombre_tag_imagen_creada>
Construir una imagen a partir de un Dockerfile (--file será necesario si el nombre del fichero no es "Dockerfile" y no está situado el mismo directorio).
docker build --tag <nombre_imagen> --file <fichero_dockerfile>
Eliminar una imagen local (-f o --force fuerza la eliminación).
docker rmi <id o nombre_imagen>
Eliminar un contenedor, si el contenedor está en ejecución será necesario detenerlo (docker stop ) antes de eliminarlo se puede indicar el parámetro -f o --force (esto fuerza la detención y eliminación del contenedor).
docker rm <id o nombre_imagen>
Eliminar todos contenedores, redes, imágenes o volúmenes "colgantes" sin referencia o no utilizados
docker system prune

#Parámetros
--all       #Eliminar todas las imágenes no utilizadas, no solo las que cuelgan.
--force     #Forzar el eliminado, no pedir confirmación.
--volumes   #Eliminar volúmenes no utilizados o colgantes.
--filter    #Proporcionar valores de filtro.

Crear e iniciar un contenedor basado en una imagen Ubuntu 18.04, que ejecuta una Shell en modo interactivo (-it) como entrypoint.
docker run –name <nombre_contenedor> -h <nombre_hostname> -it ubuntu:18.04 /bin/bash
Crear y ejecutar un contenedor con un comando/servicio en segundo plano un comando como entrypoint (-d modo detached, no interactivo) (-c comando dentro del modo interactivo bash).
docker run -d --name <nombre_contenedor> -h <hostname> <nombre_imagen> bash -c "<comando>"
docker run -d --name mi_web -h web ubuntu_web bash -c "apache2ctl -D FOREGROUND"
Crear e iniciar un contenedor en modo interactivo con una Shell (-i interactivo -t asociar una tty).
docker run -it --name test debian bash
Crear contenedores. El contenedor se crea pero no se inicia (sería necesario usar después docker start). “docker créate” dispone de multitud parámetros.
docker create -it --storage-opt size=120G fedora /bin/bash
Iniciar, detener y reiniciar contenedores.
docker start <id o nombre_imagen>
docker stop <id o nombre_imagen>
docker restart <id o nombre_imagen>
Publicar puertos en contenedores (aunque se ejecute una imagen que ya predefina exposición de puertos, igualmente habría que establecerlos en la ejecución del contenedor).
#Publicar el puerto 80 del contenedor en un puerto aleatorio del host.
docker run -it --name test -p 80 debian bash

#Publicar el puerto 80 del contenedor al 8080 del host.
docker run -it --name test -p 8080:80 debian test

#Publicar todos los puertos expuestos a puertos aleatorios del host.
docker run -it --name test -P debian bash
Listar los puertos de contenedores.
docker port <id o nombre_imagen>
Finalizar (matar) contenedores.
docker kill <id o nombre_imagen>
Buscar registros de logs de contenedores (-f muestra logs a tiempo real).
docker logs -f <id o nombre_imagen>
Mostrar los procesos en ejecución de contenedores.
docker top <id o nombre_imagen>
Buscar imágenes en repositorios (Docker Hub).
docker search <término>
Importar, exportar y guardar imágenes en un archivo tar.
docker save --outpout <nombre_contenedor> <nombre_empaquetado_contenido.tar>
docker export --output <nombre_contenedor> <nombre_empaquetado_contenido.tar>
docker import <nombre_empaquetado_contenido.tar> <nombre_contenedor>
Cargar una imagen desde un archivo tar o STDIN (Standard Input).
docker load --input <test.tar>
Gestionar Docker.
docker df                              #Mostrar el uso del disco docker.
docker system events o docker events   #Mostrar eventos en tiempo real.
docker system info o docker info       #Mostrar información de todo el sistema.
Desactivar procesos de contenedores.
docker unpause <id o nombre_imagen>
Actualizar la configuración de contenedores.
docker update <id o nombre_imagen>
Mostrar la versión de Docker.
docker version
Inspeccionar cambios de archivos o directorios de contenedores.
docker diff <id o nombre_imagen>
Inspeccionar contenedores (devuelve información de bajo nivel).
docker inspect <id o nombre_imagen>
Mostrar estadísticas de uso de los recursos de contenedores (CPU, Memoria, I/O, PIDs).
docker stats   #Muestra todos los contenedores actuales en ejecución.
docker stats <id o nombre_imagen>
Gestionar volúmenes Docker.
docker volume create    #Crear un volumen.
docker volume inspect   #Mostrar información detallada de volúmenes.
docker volume ls        #Listar volúmenes.
docker volume rm        #Eliminar volúmenes.
docker volume prune     #Eliminar volúmenes locales no utilizados.
Gestión de redes Docker.
docker network create       #Crear una red.
docker network connect      #Conectar un contenedor a una red.
docker network disconnect   #Desconectar un contenedor de una red.
docker network inspect      #Mostrar información detallada de redes.
docker network ls           #Listar redes.
docker network rm           #Eliminar redes.
docker network prune        #Eliminar todas las redes no utilizadas.
Montar un volumen de un directorio local en un contenedor (útil para trabajar con volúmenes de datos que se están editando habitualmente).
docker run -it -v /home/user/codigoWeb:/var/www/html debian bash
Crear un contenedor que usará un volumen asociado a otro contenedor existente.
#Crear el contenedor sin bash, simplemente para tenerlo como referencia para crear posteriormente otros contenedores basados en este usando su volumen de datos como referencia.
docker run -it -v /tmp:/var/www/html --name container_code debian /bin/false

#Asociar el volumen creado en el contenedor anterior con un nuevo contenedor usando el ID del contenedor.
docker run -it --name code -h code --volumenes-from ec3456a3d16cb debian bash
Copiar datos desde el contenedor al host local (opcional -a --archive copia toda la información uid/gid).
docker cp -a <id_contenedor>:<path_contenedor> <path_host_local>
Copiar desde el host local al contenedor.
docker cp -a <path_host_local> <id_contenedor>:<path_contenedor>
Ejecutar un comando en un contenedor en ejecución. Por ejemplo una Shell bash en modo interactivo (útil para acceder a un contenedor en ejecución).

https://docs.docker.com/engine/reference/commandline/exec
docker exec -it nombre_contenedor /bin/bash
docker exec -it nombre_contenedor /bin/sh
Salir de un contenedor con bash interactivo
# Con la combinación de teclas: Ctrl+p+q
# exit: si el contenedor se inició en modo --detach o -d
Live restore Docker

Permite que los contenedores permanezcan en ejecución aunque el daemon no esté disponible (por ejemplo en un reinicio del daemon dockerd). Ayuda a reducir la inactividad del contenedor debido a fallas del daemon, interrupciones planificadas o actualizaciones.

https://docs.docker.com/config/containers/live-restore

Para habilitar es necesario crear el fichero “/etc/docker/daemon.json”, con el contenido.
{
  "live-restore": true
}
Iniciar contenedores automáticamente

Iniciar automáticamente un contenedor con “--restart always” se trata de una política de reinicio que siempre reinicia el contenedor si este se detiene (útil para auto iniciar el contenedor después de reiniciar la máquina host).
docker run -d --restart always --name test -h test debian

#Políticas de reinicio automático de contenedores
no               #No reinicia automáticamente el contenedor. (valor por defecto)
on-failure       #Reinicia el contenedor si sale debido a un error.
unless-stopped   #Reinicia el contenedor a menos que se detenga explícitamente o el propio Docker se detenga o reinicie.
always           #Siempre reinicia el contenedor si se detiene.

Comandos Docker Swarm

Referencia de comandos Docker Swarm.

https://docs.docker.com/engine/reference/commandline/swarm
https://docs.docker.com/engine/reference/commandline/swarm_update

Iniciar, unirse, dejar y actualizar un cluster Swarm.
docker swarm init         #Inicializar un Swarm.
docker swarm join         #Unirse a un Swarm como nodo worker y/o manager (dependiendo si se trata de un –token manager o worker).
docker swarm leave        #Dejar un Swarm.
docker swarm update       #Actualizar un Swarm, dispone de varias opciones.
docker swarm join-token   #Gestionar join tokens


Comandos Docker Machine

Referencia de comandos Docker Machine.

https://docs.docker.com/machine/reference
docker-machine create   #Crear un nodo.
docker-machine kill     #Detener una nodo.
docker-machine ls       #Listar nodos.
docker-machine ssh      #Iniciar sesión en un nodo activo.
docker-machine rm       #Eliminar un nodo.
docker-machine env      #Establecer variables de entorno para definir que docker debe ejecutar un comando en una máquina en particular.
Crear dos nodos usando driver de Amazon Web Services, estableciendo región y zona de disponibilidad, red VPC y tamaño de disco.
docker-machine create --driver amazonec2 --amazonec2-region "us-east-2" --amazonec2-zone "b" --amazonec2-vpc-id vpc-0d1dd38... --amazonec2-root-size 8 test

Comandos Docker Node

Referencia de comandos Docker Node.

https://docs.docker.com/engine/reference/commandline/node
docker node inspect   #Mostrar información detallada en uno o más nodos.
docker node ls        #Listar nodos del Swarm.
docker node ps        #Enumerar las tareas que se ejecutan en uno o más nodos.
docker node rm        #Eliminar uno o más nodos del Swarm.
docker node update    #Actualizar un nodo.
Promover un nodo worker a manager.
docker node promote <nodo>
Degradar un nodo manager a worker.
docker node demote <nodo>
Cambiar nodo a solo Manager y no Manager+Worker (que sería la función por defecto).
docker node update --availability drain <nodo>
Docker node update

Referencia de comandos Docker node update.

https://docs.docker.com/engine/reference/commandline/node_update
--availability   #Disponibilidad del nodo ("active"|"pause"|"drain").
--label-add      #Agregar o actualizar una etiqueta de nodo (key=value).
--label-rm       #Eliminar una etiqueta de nodo si existe.
--role           #Rol del nodo ("worker"|"manager").

Comandos Docker Service

Referencia de comandos Docker Service.

https://docs.docker.com/engine/reference/commandline/service
docker service create     #Crear un nuevo servicio.
docker service inspect    #Mostrar información detallada sobre uno o más servicios.
docker service logs       #Obtener los registros de un servicio o tarea.
docker service ls         #Listar servicios.
docker service ps         #Listar las tareas de uno o más servicios.
docker service rm         #Eliminar uno o más servicios.
docker service rollback   #Revertir cambios a la configuración de un servicio.
docker service scale      #Escalar uno o múltiples servicios replicados.
docker service update     #Actualizar un servicio.
Crear un servicio. Se debe crear desde un nodo manager.
--publish: Publicar puertos
--replicas: Número de réplicas totales a repartir entre los nodos existentes.
--update-parallelism: Ejecución del número de contenedores/tareas por nodo.
--update-delay: Retraso entre las actualizaciones (ms|s|m|h)
--restart-condition on-failure: Reiniciar la tarea en caso de fallo.
--constraint node.role manager: Los nodos solo admitirán tareas de un nodo manager
test: Imagen a ejecutar.
docker service create --name test -p 80:80 --replicas 3 --update-parallelism 1 --update-delay 5s --restart-condition on-failure --constraint 'node.role == manager' test
Otra forma de crear un servicio es añadiendo \ para un salto de línea y seguir definiendo los parámetros del servicio.
docker service create \
--name test \
--publish 80:80 \
--replicas 3 \
--update-parallelism 1 \
--update-delay 5s \
--restart-condition on-failure \
--constraint 'node.role == manager' \
test

Docker service update

Referencia de comandos Docker service update.

https://docs.docker.com/engine/reference/commandline/service_update

Actualizar réplicas de un Swarm de modo que se pueda controlar un escalado elástico.
docker service update --replicas=50 test
docker service scale test=50
Forzar actualización de un servicio, incluso si ningún cambio lo requiere. Útil para actualizar un cluster de nodos Swarm en los que se actualizaron réplicas en un servicio con un paralelismo concreto y Docker Swarm ignoró el balanceo de tareas (réplicas del servicio) a nuevos nodos worker unidos al Swarm.
docker service update --force test

Comandos Docker Compose

Referencia de comandos Docker Compose.

https://docs.docker.com/compose/gettingstarted (docker-compose --help)
docker-compose up              #Crear y ejecutar los servicios y ver el proceso.
docker-compose up -d           #Crear y ejecutar los servicios en segundo plano, seguir desde la misma tty.
docker-compose run <servicio> <comando>  #Ejecutar un comando único para un servicio concreto.
docker-compose stop            #Parar todos los servicios.
docker-compose down            #Eliminar los servicios.
docker-compose down --volumes  #Eliminar los servicios y los volúmenes asociados.
docker-compose ps              #Listar los servicios.
docker-compose ps --services   #Listar solo los nombres de los servicios.
docker-compose restart         #Reiniciar los servicios.
Docker-compose rm              #Parar y eliminar los servicios.

Comandos Docker Stack

Referencia de comandos Docker Stack.

https://docs.docker.com/engine/reference/commandline/stack
docker stack deploy     #Implementar o actualizar un stack.
docker stack ls         #Lista de pilas.
docker stack ps         #Listar las tareas en la pila.
docker stack rm         #Eliminar una o más pilas.
docker stack services   #Listar los servicios en la pila.
Implementar o actualizar un stack a partir de un fichero “Docker-Compose”. Debe ejecutarse desde un nodo manager.
docker stack deploy --compose-file docker-compose.yml test
Saludos!

18 julio, 2017

Automatizar copias de seguridad FTPS en Batch y Powershell con WinSCP, Taskschd y 7zip

Hace tiempo que quería comentar el tema de como hago mis copias de seguridad de la información personal. Intentaré explicar de forma detallada y clara todo el proceso.


Se pueden realizar copias de seguridad de una manera u otra. Ya sea copiando directamente la información algún medio extraíble, subiéndola algún servicio de almacenamiento cloud, copias redundantes en ambos medios, etc. A su vez, cuantas más medidas de seguridad se tomen para cada una de estas técnicas, más segura estará la información, pero también más "pasos" habrá que desencadenar para llegar a ella.

En este caso expondré la forma en la que personalmente realizo mis copias de seguridad. Intento no utilizar, siempre que sea posible, aplicaciones locales o almacenamientos cloud de terceros bien conocidos como pueden ser: GoogleDrive, OneDrive, Dropbox, etc. Ya que actualmente son los servicios más extendidos y usados, por lo que tienen un vector de ataque continuo pero a su vez son servicios que están constantemente actualizándose, lo que los hacen ser "insuficientemente seguros".
En el caso de usar un servicio de terceros de almacenamiento cloud habría que intentar contratarlo en una empresa no tan extendida pero de confianza.

En mi caso tengo una carpeta con toda la información a respaldar, esta carpeta está ubicada en un disco duro extraíble protegido con un cifrado BitLocker To Go. Es decir, que cuando conectamos el disco duro extraíble a un puerto USB del equipo tenemos que introducir una password para desbloquearlo y así poder acceder a la información.  

Primera medida de seguridad es el disco duro extraíble por lo que nunca tengo la información en el disco local del sistema ni en un segundo disco conectado directamente a ningún puerto SATA de la placa base. 

Segunda medida de seguridad es el cifrado por BitLocker To Go. De este modo puedo llevarme el disco duro extraíble a cualquier parte y conectarlo a cualquier equipo con un sistema operativo MS Windows que admita BitLocker, sin recurrir a ningún software de terceros para el cifrado del disco, por que como ya dije, prefiero utilizar mecanismos propios de los sistemas operativos y así garantizar la disponibilidad de la información en un entorno Windows de forma nativa. No es el caso que voy a comentar pero también estaría la posibilidad de cifrar una unidad de almacenamiento externo USB con VeraCrypt. Otra buena alternativa a BitLocker To Go.

Tercer paso sería garantizar esta información en caso de pérdida, tener una redundancia. ¿Qué pasaría si en un futuro perdiese físicamente el disco duro extraíble o no pudiese acceder a el porque hubiese sido dañado?. Pues esta redundancia la tendríamos en un sitio de ubicado geográficamente distinto, un sitio de terceros. Actualmente tengo contratado un servicio de almacenamiento cloud en una empresa de Hosting, la cual es de mi confianza. Este servicio me ofrece un espacio FTP con un certificado SSL/TLS, por lo que puedo usar el canal de control y el de data para transferir información de forma segura mediante FTPS "FTP over SSL" (no confundir con SFTP "SSH-FTP"). Al realizar una sincronización con WinSCP de forma incremental cada vez que se actualizan los datos de local al servidor, los tiempos de copia son rápidos.

Otra opción en este tercer paso sería crear un fichero comprimido protegido con contraseña, generar ambos logs tanto de la compresión como de la subida al servidor FTP. El comprimir la información y cifrarla nos da una seguridad extra pero también el proceso de backup será más costoso en tiempo.

Cuarto paso sería automatizar esta tarea. Generar un script que pudiese realizar por ejemplo, una copia semanalmente de toda esta carpeta y la transfiera de forma síncrona a este espacio de almacenamiento vía FTPS. En Windows existe la característica de habilitar el cliente FTP por línea de comandos, pero no podemos usar certificados sobre este cliente. Por lo que obté en usar la herramienta WinSCP, esta incorpora una utilidad en línea de comandos (winscp.com) la cual podemos generar un script en batch en integrarla perfectamente.

Quinto paso hacer una llamada a un script de PowerShell .ps1 el cual envía el log a un correo electrónico al finalizar todo el proceso de backup con la finalidad de tener un registro de copias.

Script para el proceso de Backup y transferencia de información al servidor FTP

Si no somos usuarios administradores en el equipo local por seguridad, si no que usamos un usuario raso para el uso habitual de nuestro equipo, lo que sería más adecuado, para no tener problemas con los privilegios en el momento de ejecutar el script y las acciones que este realice, en este caso trabajaré sobre el direcctorio por defecto de instalación de WinSCP "C:\Program Files (x86)\WinSCP". Estableceremos los permisos de modificar o control total para el usuario local sobre el directorio.

Figura 1: Establecer permisos sobre el directorio donde se ejecutará el script batch.

Generamos un fichero por lotes .bat con el siguiente código:
@echo off

set ano=%date:~6,4%
set mes=%date:~3,2%
set dia=%date:~0,2%
set backup=backup_%dia%-%mes%-%ano%.log

if exist "backup*.log" ( del /F /Q "backup*.log" )

winscp.com /log="%backup%" /loglevel=2 /command "open ftp://USER:PASSWROD@IP_O_DNS_SERVER -explicit" "synchronize remote -delete -mirror -transfer=binary {PATH_LOCAL_ORIGEN} {PATH_REMOTO_DESTINO}" "close" "exit"
Figura 2: Script batch usando winscp.com para conexiones FTPS con sincronización de local a remoto.

Se establecen las variables con las que se obtendrán la fecha/hora actuales y otra (%backup%) para almacenar el nombre que tendrá el fichero con la estructura "backup_DD-MM-AAAA.log". Con un condicional se comprueba que el fichero "backup*.log" exista. Si ya existiese de una copia pasada lo eliminaría. Esta comprobación sería opcional para aquellos que quieran incluir un fichero de log. En caso de incluirlo aconsejo esta comprobación inicial, este fichero al paso del tiempo suele alcanzar un tamaño considerable, en más de una ocasión antes de haber incluido esta condición en el script llegó a ocupar tamaños superiores a 500MB, y un volumen importante de registros por lo que se hacía finalmente ilegible.

Iniciamos la utilidad winscp.com con el argumento /log para obtener un registro de la actividad realizada indicándole la variable "%backup%" correspondiente al nombre establecido que en ese momento se le asigne al fichero con fecha/hora actuales. Podemos indicar el nivel de depuración del log con el parámetro /loglevel esto nos dará más detalles de log.

La ventaja de utilizar el parámetro /log con winscp.com es que la password utilizada para la autenticación de conexión con el servidor FTP no se muestra en texto plano en el fichero de log, sino que aparace con tres asteríscos "***" sea cual sea la longitud de la contraseña. A diferencia de si se utilizara winscp.exe donde le pasaríamos el script en un fichero de texto a parte pero le indicaríamos igualmente el parámetro /log por ejemplo: winscp.exe /console /script="script_backup.txt" /log="myscript.log" (ver la figura 12 de este artículo). En este caso la contraseña en el fichero de log si se mostraría en texto plano siendo claramente visible. 

A continuación indicaremos los parámetros realmente importantes, al usar winscp.com en un script batch tendremos que indicarle el modificador /command para que sepa que los próximos modificadores serán realizados en una sola instrucción. Podríamos crear un fichero a parte con los mismos modificadores del comando necesarios, en mi caso solamente realizo una única transferencia por lo que he optado por unificarlo en un mismo fichero de procesamiento por lotes .bat.

Abrimos la conexión al servidor FTP (open) y la invocamos de forma explícita (-explicit). De esta forma el cliente solicita de forma explícita la seguridad acordada para empezar la comunicación con el servidor FTP de modo que tanto el canal de control como el de data sean cifrados.
  • synchronize remote: Los cambios del directorio local se actualizan con los remotos.
  • -delete: Elimina archivos obsoletos.
  • -mirror: Modo espejo (sincroniza también archivos antiguos).
  • -transfer=binary: Modo de transferencia binario, necesario por ejemplo para transferir ficheros de tipo imagen, vídeo, pdf, etc.
  • J:\ADRIAN: Directorio local (en este caso).
  • /Backup: Directorio remoto (en este caso).
  • Finalmente cerramos la conexión (close) y salimos de la sesión (exit).

Diferencias entre FTPS Explícito (FTPES) y FTPS Implícito

AUTH TLS o FTPS Explícito (FTPES): Definido en el RFC 2228, el cliente se conecta al puerto habitual FTP (21) y comienza una sesión FTP sin crifrar. Explícitamente cambia a un modo seguro utilizando TSL o SSL para transferir la información.

FTPS Implícito: El cliente asume el modo seguro con TSL o SSL, desde el inicio de la conexión, se realiza la negociación SSL antes de que se realice la transferencia de información. Habitualmente se utiliza el puerto 990 en vez del habitual puerto 21. Más info.

Más información sobre WinSCP en command line:
Con esto conseguiremos que cada vez que se ejecute el script este solo hará un repaso a todos los ficheros tanto locales como remotos haciendo un espejo y adaptando solamente los cambios, con lo que conseguiremos una reducción de tiempo en futuras transferencias.

Otra opción del script para el proceso de backup

La otra opción que comentaba anteriormente para este paso y que nos dará un extra de seguridad pero más tiempo de espera en el proceso del backup, sería generar un fichero comprimido .zip con la herramienta de comandos que incorpora 7-zip. Incluir en una variable de entorno de Windows el path "%programfiles%\7-zip" para que desde cualquier ubicación se reconozca el binario ejecutable de comandos "7z.exe" y así no tener problemas en la ejecución del script.
  • Comprimir el directorio en un archivo protegido por contraseña en formato .zip. Indicar un destino temporal para el fichero comprimido creado y generar un log de este proceso.
  • Realizar la subida al servidor FTP del fichero comprimo, genarar otro log de este proceso.
  • Incluir los dos logs anteriores en un único fichero de log (el cual será el que se envíe por correo).
  • Eliminar los dos logs anteriores "sobrantes" y el fichero comprimido temporal de la copia.
@echo off

:: Fecha y Hora
set ano=%date:~6,4%
set mes=%date:~3,2%
set dia=%date:~0,2%
set hora=%time:~0,8%
set backuplog=backup_%dia%-%mes%-%ano%.log
set backupzip=Backup_%dia%-%mes%-%ano%.zip

:: Credenciales y Paths
set passwd7z=passwd7z
set pathTempFichero7z="pathTempFichero7z%backupzip%"
set pathLocalDatos="pathLocalDatos"
set pathRemotoFTP=pathRemotoFTP
set usuarioFTP=usuarioFTP
set passwdFTP=passwdFTP
set servidorFTP=servidorFTP
set conexionFTP=ftp://%usuarioFTP%:%passwdFTP%@%servidorFTP%
set fingerprintSSLFTP="xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"

:: Comprobar si existen ficheros de log pasados del backup.
if exist "*backup*.log" ( del /F /Q "*backup*.log" )

:: Mostrar fecha y hora del comienzo del proceso de backup al princpio del log.
echo El backup comienza: %dia%-%mes%-%ano% - %hora% > %backuplog%
echo. >> %backuplog%
echo # # # # # # # # # # # # # # # # # # # # >> %backuplog%

:: Comprimir datos, generar log zip, agregarlo al backup log final y mostrar una línea de separación.
:: En caso de excluir directorios y ficheros añadir a la línea de 7z el parámetro -xr!"<directorio_fichero>" tantas veces como elementos a excluir de la compresión.
7z a -tzip -p%passwd7z% -r %pathTempFichero7z% %pathLocalDatos% > zip%backuplog%

type zip%backuplog% >> %backuplog%
echo. >> %backuplog%
echo # # # # # # # # # # # # # # # # # # # # >> %backuplog%

:: Subir el fichero comprimido al servidor FTP, generar log FTP, añadirlo al log de backup y mostrar una línea de separación.
winscp.com /log="ftp%backuplog%" /loglevel=2 /command "open %conexionFTP% -explicit -certificate=%fingerprintSSLFTP%" "cd %pathRemotoFTP%" "rm Backup*.zip" "put %pathTempFichero7z%" "close" "exit"

type ftp%backuplog% >> %backuplog%
echo. >> %backuplog%
echo # # # # # # # # # # # # # # # # # # # # >> %backuplog%

:: Eliminar ficheros temporales: logs y fichero temporal backup zip.
del /F /Q "zip*.log"
del /F /Q "ftp*.log"
del /F /Q %pathTempFichero7z%

:: Comprobar la eliminación de ficheros de log y fichero temporal backup zip, insertar el resultado en el log.
:: Log de la compresión zip.
if exist "zip*.log" (
    echo -- zip%backuplog% no se eliminó correctamente >> %backuplog%
    ) else (
       echo -- zip%backuplog% se eliminó correctamente >> %backuplog%
    )
:: Log del envío de datos al servidor FTP.
if exist "ftp*.log" (
    echo -- ftp%backuplog% no se eliminó correctamente >> %backuplog%
    ) else (
       echo -- ftp%backuplog% se eliminó correctamente >> %backuplog%
    )
:: Fichero temporal backup zip
if exist "D:\Backup*.zip" (
    echo -- %backupzip% no se eliminó correctamente >> %backuplog%
    ) else (
       echo -- %backupzip% se eliminó correctamente >> %backuplog%
    )

echo. >> %backuplog%
echo # # # # # # # # # # # # # # # # # # # # >> %backuplog%
:: Mostrar fecha y hora de la finalización del proceso de backup al final del log.
:: Se resetea la variable hora para obtener la hora actual hasta este momento del proceso de backup.
set hora=%time:~0,8%
echo El backup finaliza: %dia%-%mes%-%ano% - %hora% >> %backuplog%

:: Llamada al script Powershell para el envío del log vía Email.
powershell.exe -file "envio_log_email.ps1"
Los parámetros de compresión usando "7z.exe" dependerá de las opciones que cada uno quiera establecer, en mi caso.
  • a: añade información (comprime)
  • -t: indica el tipo de formato (zip en este caso)
  • -r: Recursividad en subdirectorios.
  • -p: Password para cifrar el fichero comprimido
Un ejemplo sería.

Figura 3: Otra opción para el proceso de backup. Comprimir la información, cifrarla, enviarla al FTP y borrar los ficheros temporales.

Realizará una comprobación de si existe ya algún fichero log pasado, si existe los eliminará y generará también dos log (temporales), uno para el proceso de compresión de la información y otro log para el proceso de transferencia de información al servidor FTP. Finalmente generará un único fichero con los dos logs anteriores juntos que será el que se envíe por correo a través de la llamada al script de PowerShell el cual hará el proceso de envío (este punto está detallado en el último apartado de este artículo).

Inicialmente, justo antes de la compresión, añado la fecha y hora actual en el principio del fichero de log principal para saber cuando se inició la copia, ya que el log de compresión con 7z.exe no muestra valores de tiempo en su proceso. Es el momento en el que se crea el fichero de log principal y final, que se enviará por correo.

Después eliminará los logs temporales creados y el fichero comprimido temporal cuando ya haya sido subido al servidor FTP.

Opcionalmente se hacen unas comprobaciones para saber si se eliminaron tanto los logs temporales como el fichero comprimido temporal y adjuntar este resultado al log de backup principal con formato de fecha actual. Finalmente se envía este fichero de log final a un correo electrónico (script PowerShell detallado al final de este artículo).

Comentar que para este estilo de copia (compresión de la información y subida al servidor sin usar sincronización con WinSCP) el tiempo total del backup desde la compresión del archivo a la subida al servidor FTP, es de unas 2 horas y 25 minutos aprox. (quizás algo menos dependiendo el tipo de conexión que tengamos y los límites al servidor FTP si es que los hay) para un tamaño total de copia de unos 45GB.

Crear una tarea programada para el proceso de backup

Una vez que tenemos generado el script, tendremos que ejecutarlo manualmente y eso no sería práctico, llegados a este punto lo mejor será automatizar este script para que por ejemplo se ejecute un día y hora en concreto de la semana.
Para no recurrir a herramientas de terceros usaremos el "Programador de tareas" de Windows (taskschd.msc).

Creamos una nueva tarea. Como desencadenador será según una programación, semanalmente los Domingos a las 22:00. En mi caso lo tengo así programado ya que se que ese día de la semana y a esa hora suelo tener equipo encendido.

Figura 4: Estableciendo el desencadenador de la tarea programada.

Las acción será iniciar un programa o script, indicamos el path de la ruta donde se encuentra el script. En mi caso no quería crear una variable de entorno de Windows por lo que tengo que indicarle al sistema donde se encuentra la utilidad winscp.com, por lo que le indico donde se debería iniciar el path en "Iniciar en".

Figura 5: Estableciendo la acción para ejecutar el script.

La configuración de la tarea programada la establecí del siguiente modo. Si la tarea no se ejecuta, reiniciarla cada 1 minuto durante un número de intentos de 5 veces y si sobrepasa las 12 horas de ejecución detener la tarea.

Figura 6: Configuración de la tarea programada.

Finalmente en la pestaña general designamos un nombre a la tarea programada. Una vez llegados a este punto tendremos dos opciones:

- Ejecutar solo cuando usuario haya iniciado sesión: En este caso veremos la ventana de la consola de Windows ejecutando todo el proceso.

- Ejecutar tanto si el usuario inició sesión como si no: En este caso la tarea se ejecutará de forma subyacente al usuario. Aconsejo esta opción ya que nos garantiza la ejecución de la tarea tan solo con tener encendido el equipo. Por lo que pude comprobar al intentar ejecutar esta tarea de forma manual a través del programador de tareas puede que falle. Sin embargo, al ejecutarse automáticamente según la programación establecida se ejecutará sin problemas.

Si escogemos la primera opción no tendremos problemas. Pero si escogemos la segunda opción ya que puede darse el caso de que no estemos con la sesión iniciada en el equipo pero el equipo esté encendido (y con el BitLocker desbloqueado en este caso). Al intentar establecer la tarea nos dirá que "la cuenta de usuario especificada tenga derechos para iniciar sesión como trabajo por lotes". Esto ocurre por que en mi caso el usuario que habitualmente uso en el sistema por seguridad no está dentro del grupo Administradores, sino dentro del grupo Usuarios y por lo tanto no tiene los suficientes permisos como para llevar a cabo esta acción.

Figura 7: Ejecutar tarea programada tanto si el usuario inicia sesión como si no.

Para ello solamente tendremos que agregar la cuenta de usuario en el editor de directivas seguridad local (a través de la consola de Microsoft "gpedit.msc" o directamente "secpol.msc") buscamos "Iniciar sesión como proceso por lotes" y agregamos el usuario.

Figura 8: secpol.msc - Iniciar sesión como proceso por lotes para una tarea programada.

De este modo ya podremos autenticar con las credenciales del usuario local, independientemente de si se marca el checkbox de "No almacenar contraseña" como si no.

Más información: https://technet.microsoft.com/en-us/library/cc722152(v=ws.11).aspx

Figura 9: Autenticación de credenciales del usuario local para tarea programada.

Para poder hacer un seguimiento de la ejecución de la tarea programada habilitamos el historial. Esta opción afectará a todas las tareas programadas que tengamos en la biblioteca.

Figura 10: Habilitar el historial de las tareas programadas.

Una vez se ejecute y concluya la tarea, en la pestaña de historial veremos la auditoria de información de tiempos. Para hacerse una idea, en mi caso, según mi conexión de DSL y el ancho de banda contratado con el Hosting que me proporciona el server FTP. En menos de 45 minutos se hicieron transferencias de un total aproximado de 50GB (aunque esto dependerá del tipo de conexión que tengamos así como si hay algún límite de súbida con el servidor FTP).

Figura 11: Historial de la tarea programada.

Si consultamos el fichero .log que creamos para el registro de actividad de conexiones y transferencias hacia el servidor FTPS. Vemos como la conexión se realiza de forma explícita solicitando el certificado al servidor FTP y finalmente se establece la conexión TLS.

Figura 12: Fichero de log de las transferencias realizadas al servidor FTPS.

Enviar el log por correo electrónico con un Script automatizado en PowerShell

Mencionar la posibilidad de poder auto-enviarse este log generado anteriormente a través de un correo electrónico y así tener un registro más clasificado por fechas.

Se me ocurrió crear un fichero .ps1 (PowerShell) que será el script que enviará el mensaje de correo electrónico con el fichero log como adjunto. E incluir una llamada a al fichero .ps1 al final del script batch que hace la copia de seguridad con WinSCP. De ese modo se garantiza que el fichero log esté finalizado. Con este método no se tendrá que modificar la tarea programada.

Habilitar la ejecución de scripts para Powershell

Para poder ejecutar scripts en PowerShell sin problemas, se tendrá que cambiar la política de ejecución (ExecutionPolicy). En un principio no están definidas y por seguridad vienen deshabilitados todos los tipos de ejecuciones para los scopes.

Existen varios modos de políticas de ejecución para definir en PowerShell:

  • Restricted: Bloquea todos los scripts en PowerShell y hace que todas las tareas deban ejecutarse de forma interactiva, nada automático. Es la política por defecto.
  • Unrestricted: Puede ejecutar cualquier script, sin restricciones. Si se ejecuta un script sin firmar muestra una advertencia al usuario.
  • RemoteSigned: Puede ejecutar scripts que han sido creados en la máquina local sin ser firmados. Pero los paquetes descargados deben estar firmados antes de poder instalarlos. Conlleva el riesgo de que no es necesario que los scripts locales vayan firmados digitalmente. Para ejecutar scripts descargados de internet (sin firmar) es necesario usar la opción Unblock-File. Es la política por defecto para Windows Server.
  • AllSigned: Solo puede ejecutar paquetes y scripts firmados digitalmente por un publicador de confianza, incluidos los scripts escritos en el equipo local.
  • Bypass: Similar a Unrestricted, con la diferencia de que no alerta de riesgos al usuario. Suele utilizarse en integraciones de PowerShell con otras aplicaciones, en las que funciona en una capa inferior, dado que dichas aplicaciones cuentan con un modelo de seguridad propio.
  • Undefined: No se establece explícitamente ninguna de las directivas anteriores. Por defecto sería Restricted.
  • Default: Establece la política de ejecución predeterminada. Restricted para clientes de Windows y RemoteSigned para Windows Server
Para esta situación puede servir tanto el modo de ejecución RemoteSigned como Unrestricted.

Listamos el tipo de políticas de ejecución y esteblecemos el tipo de política para el scope LocalMachine como Unrestricted. Aunque también sería válido para este caso RemoteSigned. Aclarar que esto no es una forma segura.
Get-ExecutionPolicy -List
Set-ExecutionPolicy Unrestricted -scope LocalMachine -Force
Figura 13: Estableciendo el modo de política de ejecución (ExecutionPolicy) para PowerShell

Lo óptimo sería ejecutar desde el fichero bat la llamada al fichero ps1, habilitando únicamente en ese contexto el Bypass para la ejecución de scripts en Powershell.
PowerShell.exe -ExecutionPolicy Bypass -File .script.ps1
Una vez se puedan ejecutar scripts en PowerShell se crea un documento de texto en el mismo directorio y se renombra con una extensión .ps1 (extensión usada por PowerShell) por ejemplo "envio_log_email.ps1". Se inserta el siguiente código.
# Variables
$fechaHoraActual = Get-Date -uformat "%d/%m/%Y - %H:%M:%S"
$usuarioEmail = "usuarioEmail"
$passwdEmail = "passwdEmail"
$asuntoEmail = "asuntoEmail"
$cuerpoEmail = "cuerpoEmail"
# Convertir password a un string seguro.
$secPasswdEmail = ConvertTo-SecureString $passwdEmail -AsPlainText -Force
$credencialesEmail = New-Object System.Management.Automation.PSCredential ($usuarioEmail, $secPasswdEmail)

# Enví­o del fichero log adjunto viía Email usando Gmail.
Send-MailMessage -From $usuarioEmail -To $usuarioEmail -Subject "$asuntoEmail - $fechaHoraActual" -Body "$cuerpoEmail - $fechaHoraActual" -Attachments "backup*.log" -SmtpServer smtp.gmail.com -UseSsl -Credential $credencialesEmail
exit
Un ejemplo de este script en Powershell ps1:

Figura 14: Script ps1 Powershell - Envío del fichero de log a una cuenta de correo Gmail.

Este script en Powershell auto-enviará un mensaje de correo a una misma cuenta de correo electrónico u otra si lo establecemos.

La variable "$fechaHoraActual" alamacena la función "Get-Date" usando el parámetro "-uformat" seguido de la estructura fecha y hora "DD/MM/AAAA - HH:MM:SS" esto se añadirá en el asunto y cuerpo del mensaje, después del nombre del fichero de log, reflejará el momento actual en el que ha sido enviado este adjunto. Se almacena también la contraseña en la variable "$passwd" en una función "ConvertTo-SecureString" que se pasará en una cadena de texto segura. La ventaja de incluirla la hora es que se puede calcular el tiempo transcurrido en el proceso de backup desde el momento en el que se ejecutó la tarea programada hasta el momento en el que se recibe el mensaje de correo electrónico. Esta información también se podría ver en el historial del programador de tareas como se puede observar en la figura 10 de este artículo. 
  • usuarioEmail: Se cambiará por el nombre de usuario de la dirección de correo electrónico correspondiente.
  • passwdEmail: Password del usuario (dirección del correo electrónico) necesaria para autenticarnos en el servidor de correo.
  • asuntoEmail: Texto del asunto del Email (-Subject).
  • cuerpoEmail: Texto del cuerpo del Email (-Body).
  • backup*.log: Nombre que corresponde al fichero de log adjunto a enviar en el mensaje.
En mi caso usaré Gmail como servidor SMTP. En un principio no va poder autenticarse aunque se le pasen correctamente las credenciales de usuario. Esto es debido a la protección de seguridad que implementa Gmail para evitar el inicio de sesión de aplicaciones de terceros menos seguras.

Para poder autenticarse con PowerShell en una cuenta Gmail tan solo se tendría que activar el acceso a "Aplicaciones menos seguras". En el siguiente enlace podemos acceder a esta sección: https://myaccount.google.com/lesssecureapps.

En el caso de tener activado un segundo factor de autenticación (2FA) con Google Authenticator, esta característica estaría deshabilitada y no se podría realizar este proceso.

Figura 15: Activar el acceso a aplicaciones menos seguras en una cuenta Gmail

En el script batch inicial donde se realiza el backup con WinSCP se añade al final la siguiente línea de código indicándole el que le ejecute con powershell, el argumento -file hará la llamada al script .ps1 desde un script .bat. De este modo no se tendrá que modificar la tarea programada.
powershell.exe -file "{PATH_FICHERO.ps1}"
Figura 16: Llamada a un fichero script .ps1 desde un script.bat.

Se puede ver el mensaje de correo electrónico que fue auto-enviado a uno mismo con un fichero adjunto "backup_DD-MM-AAAA.log" donde se muestra la fecha y hora de envío en el cuerpo del mensaje en formato "DD/MM/AAAA - HH:MM:SS".

Figura 17: Correo enviado con Powershell y recibido en Gmail con fecha y hora del log.

El procedimiento de enviarse el log por correo electrónico a través de PowerShell simplemente lo menciono por si es de interés para alguien. Personalmente en mis cuentas de correos Gmail, Hotmail, Yahoo. Tengo habilitado un segundo factor de autenticación, considero que es lo más seguro y que todo el mundo que quiera tener sus cuentas más seguras use, en la medida de lo posible, más de un factor de autenticación.

En el caso de tener una cuenta de correo Gmail por ejemplo con autenticaciones 2FA. Aconsejo crear una cuenta de correo exclusivamente para esta finalidad y configurar las opciones de reenvío de mensajes de esa cuenta para que reenvíe a otra cuenta más personal estos correos, en la que si se tenga habilitada la autenticación 2FA.

Script Powershell usando WinSCP y 7zip

Hice también un script muy similar en Powershell. Con dos diferencias (no muy relevantes).
  • No es posible generar el log del proceso de compresión de datos que realiza 7zip.
  • Si queremos establecer password al fichero comprimido, el formato de fichero debe ser .7z. El uso del cmdlet "Compress-7Zip" no permite establecer password a ficheros de formato .zip.
Primero de nada quitamos las restricciones de ejecución de scripts ejecutando como administrador una consola Powershell.
Set-ExecutionPolicy Unrestricted -scope LocalMachine -Force
Instalamos los modulos 7Zip4Powershell y WinSCP para Powershell.
Install-Module -Name 7Zip4Powershell -Verbose -Force
Install-Module -Name WinSCP -Verbose -Force
Por defecto estos módulos se instalan en "%systemdrive%:\Program Files\WindowsPowerShell\Modules".
  • Install-Module: instala definitivamente módulo para todo equipo. 
  • Import-Module: instala el módulo solo para la sesión actual de usuario de Powershell.
Script .ps1 Powershell
#################################
## Inicio Establecer Variables ##

# Fecha y hora
    $FechaActual = Get-Date -uformat "%d-%m-%Y"
    $FechaHoraActual = Get-Date -uformat "%d/%m/%Y - %H:%M:%S"
# Paths
    $PathLocalDatos = "PathLocalDatos"
    $PathRemotoFTP = "PathRemotoFTP"
    $PathTempFichero7z = "PathTemporalFichero7z"
    $NombreBackupTemp = "Backup_"
    $TempFichero7z = "$PathtempFichero7z$NombreBackupTemp$FechaActual.7z"
# Credenciales
    $Passwd7z = "PasswdFichero7z"
    $HostServidorFTP = "ftp.miweb.com"
    $UsuarioFTP = "UsuarioFTP"
    $PasswdFTP = "PasswdFTP"
    $UsuarioEmail = "UsuarioEmail@gmail.com"
    $PasswdEmail = "PasswdEmail"
# Asunto y cuerpo del Email
    $AsuntoEmail = "AsuntoEmail"
    $CuerpoEmail = "TextoCuerpoEmail"
# Get-Credencial automatizado no interactivo, convertir a string segura
    $SecPasswdFTP = ConvertTo-SecureString $PasswdFTP -AsPlainText -Force
    $CredencialesFTP = New-Object System.Management.Automation.PSCredential ($UsuarioFTP, $SecPasswdFTP)
    $SecPasswdEmail = ConvertTo-SecureString $PasswdEmail -AsPlainText -Force
    $CredencialesEmail = New-Object System.Management.Automation.PSCredential ($UsuarioEmail, $SecPasswdEMail)
# Log
    $LogBackupFTP = "$PathtempFichero7z$NombreBackupTemp$FechaActual.log"
# Comprobaciones Test-Path
    $TestBackup7z = "$PathTempFichero7z$NombreBackupTemp*.7z"
    $TestBackupLog = "$PathTempFichero7z$NombreBackupTemp*.log"

## Fin Establecer Varibles ##
#############################

# Comprobrar si ya existe algún fichero de log o backup anteriores
if (Test-Path ($TestBackup7z, $TestBackupLog)) {
    Remove-Item -Path ($TestBackup7z, $TestBackupLog) -Recurse -Force
    }

## Compresión de datos ##
Compress-7Zip -Path $pathLocalDatos -ArchiveFileName $TempFichero7z -Password $Passwd7z -EncryptFilenames

## Enviar backup al servidor FTP ##
    # Crear nueva sesión FTP
    New-WinSCPSession -SessionOption (New-WinSCPSessionOption -HostName $HostServidorFTP -Protocol Ftp -Credential $CredencialesFTP) -SessionLogPath $LogBackupFTP -DebugLogLevel 2
    # Eliminar el viejo fichero comprimido de datos del servidor FTP
    Remove-WinSCPItem -Path $PathRemotoFTP/Backup*.7z
    # Subir el nuevo fichero comprimido de datos al servidor FTP
    Send-WinSCPItem -LocalPath $TempFichero7z -RemotePath $PathRemotoFTP
    # Cerrar sesión FTP
    Remove-WinSCPSession

## Enviar log de backup vía email Gmail ##
Send-MailMessage -From $UsuarioEmail -To $UsuarioEmail -Subject "$AsuntoEmail - $FechaHoraActual" -Body "$CuerpoEmail - $FechaHoraActual" -Attachments $LogBackupFTP -SmtpServer smtp.gmail.com -UseSsl -Credential $CredencialesEmail

## Eliminar logs ##
# Conservar el fichero de log, eliminar solo el fichero temporal backup 7z
Remove-Item -Path $TempFichero7z -Recurse -Force
# En el caso de querer eliminar también el log de backup, descomentar la siguiente línea
# Remove-Item -Path $logBackupFTP -Recurse -Force

# Salir
exit
Adaptamos el código modificando los valores resaltados en negrita de las variables. Lo guardmos en un fichero con extensión .psi y susituímos el batch incluído en la tarea programada de Windows por el script Powershell.

Figura 18: Script ps1 en Powershell. Backup completo + envío de log vía email.

Espero que algo de esto le resulte de utilidad o de ejemplo para aquell@s que quieran realizar sus propios métodos para sus copias de seguridad.

Saludos!

Entradas Populares