Powershell Series II: Modules y gestor de credenciales
Continuamos con esta serie de artículos dedicados a Powershell, tenéis disponible el primer artículo aquí. Hoy vamos a ver cómo crear nuestros propios módulos que podemos tener disponibles para cargar a voluntad.
Tras la última guía, nuestro archivo de perfil de powershell incluía estas dos líneas:
$modules = "$(split-path $profile)\Modules"
$env:PsModulePath += ";$modules"
Por lo tanto, podríamos cargar un módulo que se encontrara en la carpeta:
%UserProfile%\Documents\WindowsPowershell\modules
Pero, ¿Qué es un módulo? Quizá no sea la definición más correcta, pero podemos entender un módulo como un componente que va a definir nuevos cmdlets, funciones, variables y otros objetos de powershell. Aunque hay muchos tipos de módulos, nos vamos a centrar en módulos de tipo script, ya que van a ser los más sencillos de desarrollar.
Conceptualmente, la diferencia entre script y modulo es que el script se escribe para que ejecute ciertas acciones, mientras que el modulo define acciones que se pueden usar en el futuro (directamente por el usuario o por un script)
Vamos a trabajar en un ejemplo. Pensemos cuando queremos conectar a Office 365 mediante powershell. Podemos tener que conectar a la parte de Exchange Online, de Lync Online o de Azure Online. Podemos crear una serie de scripts que nos realicen la conexión a estos servicios. Son acciones que quiero que se ejecuten bajo demanda. Estos comandos serán similares a:
$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session
No hay nada malo con este script, sin embargo, si realizamos muchas conexiones distintas a Office 365 a lo largo del día, a distintos componentes, puede que terminemos cansados de tener que estar continuamente introduciendo las credenciales. Si además tenemos más de un entorno al que conectar, o realizamos operaciones con distintos usuarios, tendremos que recordar más credenciales. Una solución sencilla puede ser tener un gestor de credenciales que las almacene y proporcione una serie de funciones para interactuar con el gestor. Este fichero no va a realizar ninguna acción directa, únicamente definir otras funciones, por lo que tiene más sentido desarrollarlo como un módulo que como un script.
Idealmente, tendremos una función llamada Load-Credential a la que le pasaremos un nombre de usuario. Si dicho usuario existe en nuestro gestor de credenciales, la función devolverá un objeto de tipo PSCredential, el mismo tipo que devuelve el comando Get-Credential y que otros cmdlets como New-PSSession esperan recibir. Para que esto funcione, necesitaremos algunos comandos adicionales:
Load-Credential: Comentado anteriormente, recibe un nombre de usuario y devuelve un objeto PSCustomObject
Store-Credential: Para guardar unas credenciales de un usuario en el almacén
Remove-Credential: Eliminar las credenciales
Check-Credential: Comprobar si las credenciales existen o no. Adicionalmente, podemos comprobar que no hayan expirado (si las credenciales son muy viejas, quizá no merezca la pena trabajar con ellas)
Nuestro gestor de credenciales necesitara dos cosas adicionales. Por un lado, un sitio donde guardar las contraseñas de forma segura y adicionalmente, una forma de indicar cuanto tiempo es demasiado tiempo para reutilizar una contraseña (una fecha de expiración para las mimas).
Almacenar contraseñas de forma completamente segura es algo complicado. En el fondo, si el script es capaz de leer las contraseñas y utilizarlas, cualquier persona con la capacidad de ejecutar el script, tiene que poder leer dichas contraseñas. Sin embargo, eso no significa que dichas contraseñas puedan ser leídas por cualquier persona con acceso al ordenador. Windows tiene un componente llamado DataProtectionAPI (DPAPI) que permite cifrar datos de nuestro ordenador con una clave que va asociada a nuestra contraseña de usuario. Powershell provee de un comando, Convert-FromSecureString, que nos permite almacenar una SecureString (el objeto donde powershell almacena nuestra contraseña) como una cadena de texto cifrada mediante DPAPI. Solo el usuario que ha generado la cadena de texto cifrada puede descifrarla.
Con esto resuelto, podemos guardar nuestras contraseñas en un fichero con el nombre del usuario (o algo derivado a partir de él) que incluya esta contraseña cifrada y la fecha en la que se almaceno, generando un fichero similar a:
"username","password","date"
"test1","...04fc...","20150328"
Una vez hecho esto, podemos empezar a escribir las funciones. Solo voy a poner una de ejemplo aquí, pero tenéis el resto en el fichero adjunto al artículo
function Check-Credential
{
Param([string] $username)
$hash = $username.ToLower().GetHashCode().ToString("X2")
$path = $credfolder + "\" + $hash
if (![System.IO.File]::Exists($path)) {return $null}
$data = import-csv $path
$expireDate = (get-date).AddDays(-$CredentialExpirationDays).ToString("yyyyMMdd")
if ($data.date -lt $expireDate) {return $false}
return $true
}
Aquí empieza la parte de escribir las funciones. En este caso, hemos definido la función Check-Credential especificando que tiene un parámetro de tipo string llamado $username. Nuestro objetivo será si en la carpeta definida por $credfolder existe un fichero con la contraseña de dicho usuario. Los ficheros no se llaman exactamente igual que el usuario (esto puede traer problemas si el usuario incluye información del dominio en sintaxis <dominio>\<usuario>, ya que “\” no es un carácter válido para el nombre de un fichero. Para ello, hacemos uso de una función nativa de .NET que todos los objetos (incluidos los de powershell) tienen llamada GetHashCode que genera un número de 32 bits. Aunque este número no es único, la posibilidad de una colisión entre dos cadenas de texto es bastante extremadamente baja (si encontrais una colisión, avisadme y podemos modificar el módulo para que tenga esto en cuenta. Si el fichero no existe, devolveremos null (Algo que comprobamos llamando a [System.IO.File]::Exists($path) y que en futuros episodios entraremos en más detalle) y si existe, en función de la fecha presente en el fichero y la varible $expireData devolveremos $true o $false.
El resto de funciones las tenéis en el fichero CredMan.psm1
Una vez que tenemos este fichero escrito, es muy sencillo construir un módulo, no tenemos más que generar una carpeta con el nombre del módulo en la ruta $modules que definimos anteriormente y nuestro módulo ya está disponible para ser usado. Si hacemos una llamada a:
Get-Module –ListAvailable
Recibiremos la lista de módulos en las distintas carpetas (incluida la que hemos definido nosotros):
Directory: C:\Users\drodrig\Documents\WindowsPowerShell\Modules
ModuleType Version Name ExportedCommands
---------- ------- ---- ----------------
Script 0.0 Credman {Store-Credential, Load-Credential, Remove-Credential, Check-Credential}
Con esto, ya podemos empezar a hacer uso del gestor de credenciales. Recordad crear la carpeta a la que apunta la variable $CredFolder (En mi caso, una carpeta llamada Credentials dentro de la carpeta WindowsPowershell) y podemos empezar a llamar a las nuevas funciones que hemos definido.
Tras almacenar una serie de credenciales, tendremos un resultado similar a:
Y aquí teneis un ejemplo en funcionamiento:
PS C:\> Store-Credential myuser@contosolab.com
Please, insert password for user myuser@contosolab.com
Password stored for user myuser@contosolab.com
PS C:\> Check-Credential myuser@contosolab.com
True
PS C:\> $cred = Load-Credential myuser@contosolab.com
Password loaded for user myuser@contosolab.com
Ahora bien, recordad una vez más que este gestor de credenciales es tan seguro como tu sesión de Windows. Si alguien tiene acceso a tu sesión de Windows, tiene acceso a las contraseñas que hayas almacenado (similar a las contraseñas almacenadas en el navegador)
Adicionalmente, aunque un objeto PSCredential almacene la contraseña en un objeto SecureString, esto no significa que no podamos obtener la versión legible de dicha contraseña. Si tenemos una variable $cred que contiene un objeto PSCredential, recuperar la contraseña es tan sencillo como:
PS C:\> $cred.GetNetworkCredential().Password
Tarta
Con esto hemos acabado el artículo de hoy. El siguiente en la serie será como crear scripts de conexión a Office 365 que hagan uso de este gestor de credenciales
Saludos a todos,
David R.