Acceso desde windows a Openstack con ssh


La mayor parte de la autenticación en entornos de Windows se realiza con un par de nombre de usuario y contraseña. Esta funciona bien en los sistemas que comparten un dominio común. Al trabajar con varios dominios (por ejemplo, entre sistemas locales y hospedados en la nube), se expone más a intrusiones de fuerza bruta.

En comparación, los entornos de Linux, normalmente, usan pares de clave pública y privada para controlar la autenticación, lo que no requiere el uso de contraseñas que se puedan adivinar. OpenSSH incluye herramientas que ayudan a admitir esta opción, en concreto:

  • ssh-keygen para generar claves seguras.
  • ssh-agent y ssh-add para almacenar claves privadas de forma segura.
  • scp y sftp para copiar archivos de claves públicas de forma segura durante el uso inicial de un servidor.

En este documento se proporciona información general sobre cómo usar estas herramientas en Windows para empezar a usar la autenticación de claves con SSH. Si no estás familiarizado con la administración de claves SSH, se recomienda que revises el informe interno de NIST 7966 titulado «Seguridad de la administración de acceso interactiva y automatizada con Secure Shell (SSH)».

Acerca de los pares de claves

Los pares de claves hacen referencia a los archivos de clave pública y privada que utilizan determinados protocolos de autenticación.

La autenticación de clave pública SSH usa algoritmos criptográficos asimétricos para generar dos archivos de clave: uno «privado» y otro «público». Los archivos de clave privada son el equivalente de una contraseña y deben estar protegidos en todo momento. Si alguien adquiere tu clave privada, esa persona puede iniciar sesión en cualquier servidor SSH al que tengas acceso. La clave pública es la que se coloca en el servidor SSH y puede compartirse sin poner en peligro la clave privada.

Al usar la autenticación de claves con un servidor SSH, el servidor SSH y el cliente comparan las claves públicas del nombre de usuario proporcionado con la clave privada. Si la clave pública del lado servidor no se puede validar con la clave privada del lado cliente, se produce un error de autenticación.

Para implementar la autenticación multifactor con pares de claves, debes solicitar que se proporcione una frase de contraseña cuando se genera el par de claves (consulta Generación de claves a continuación). Durante la autenticación, se solicita al usuario la frase de contraseña, que se usa junto con la presencia de la clave privada en el cliente SSH para autenticar al usuario.

Generación de claves de host

Las claves públicas tienen requisitos de ACL específicos que, en Windows, equivalen a permitir el acceso únicamente a los administradores y al sistema. Para facilitar esta tarea,

  • El módulo de PowerShell de OpenSSHUtils se creó para establecer las ACL de claves correctamente y debe instalarse en el servidor.
  • Al usar sshd por primera vez, se generará automáticamente el par de claves para el host. Si el ssh-agent se está ejecutando, las claves se agregarán automáticamente al almacén local.

Para facilitar la autenticación de claves con un servidor SSH, ejecuta los siguientes comandos desde un símbolo del sistema de PowerShell con privilegios elevados: PowerShell

# Install the OpenSSHUtils module to the server. This will be valuable when deploying user keys.
Install-Module -Force OpenSSHUtils -Scope AllUsers

# By default the ssh-agent service is disabled. Allow it to be manually started for the next step to work.
Get-Service -Name ssh-agent | Set-Service -StartupType Manual

# Start the ssh-agent service to preserve the server keys
Start-Service ssh-agent

# Now start the sshd service
Start-Service sshd

Dado que no hay ningún usuario asociado al servicio sshd, las claves de host se almacenan en \ProgramData\ssh.

Generación de claves de usuario

Para usar la autenticación basada en claves, primero debes generar algunos pares de claves públicas o privadas para el cliente. Desde PowerShell o cmd, usa ssh-keygen para generar algunos archivos de clave. PowerShell

cd ~\.ssh\
ssh-keygen

Esta operación debería mostrar algo similar a lo siguiente (donde «username» se reemplaza por tu nombre de usuario)

Generating public/private ed25519 key pair.
Enter file in which to save the key (C:\Users\username\.ssh\id_ed25519):

Puede presionar Entrar para aceptar el valor predeterminado o puedes especificar una ruta de acceso en la que quieres que se generen las claves. Llegados a este punto, se te pedirá que uses una frase de contraseña para cifrar los archivos de clave privada. La frase de contraseña funciona con el archivo de clave para proporcionar una autenticación en dos fases. En este ejemplo, vamos a dejar la frase de contraseña vacía.

Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in C:\Users\username\.ssh\id_ed25519.
Your public key has been saved in C:\Users\username\.ssh\id_ed25519.pub.
The key fingerprint is:
SHA256:OIzc1yE7joL2Bzy8!gS0j8eGK7bYaH1FmF3sDuMeSj8 username@server@LOCAL-HOSTNAME

The key's randomart image is:
+--[ED25519 256]--+
|        .        |
|         o       |
|    . + + .      |
|   o B * = .     |
|   o= B S .      |
|   .=B O o       |
|  + =+% o        |
| *oo.O.E         |
|+.o+=o. .        |
+----[SHA256]-----+

Ahora tienes un par de claves ED25519 pública y privada (los archivos .pub son claves públicas y los demás son claves privadas):

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        9/28/2018  11:09 AM           1679 id_ed25519
-a----        9/28/2018  11:09 AM            414 id_ed25519.pub

Recuerde que los archivos de clave privada son el equivalente de una contraseña, por lo que se deben proteger de la misma manera que las contraseñas. Para que sea más fácil, usa ssh-agent para almacenar de forma segura las claves privadas en un contexto de seguridad de Windows y asócialas con tu inicio de sesión de Windows. Para ello, inicia el servicio de ssh-agent como administrador y usa ssh-add para almacenar la clave privada. PowerShell

# Make sure you're running as an Administrator
Start-Service ssh-agent

# This should return a status of Running
Get-Service ssh-agent

# Now load your key files into ssh-agent
ssh-add ~\.ssh\id_ed25519


Después de completar estos pasos, cada vez que se necesite una clave privada para la autenticación desde este cliente, ssh-agent recuperará automáticamente la clave privada local y la pasará al cliente SSH.

Nota

Se recomienda realizar una copia de seguridad de la clave privada en una ubicación segura y, a continuación, eliminarla del sistema local después de agregarla al agente SSH. No se puede recuperar la clave privada del agente. Si pierdes el acceso a la clave privada, tendrás que crear un nuevo par de claves y actualizar la clave pública en todos los sistemas con los que interactúes.

Implementación de la clave pública

Para usar la clave de usuario que se creó anteriormente, la clave pública debe colocarse en el servidor en un archivo de texto denominado authorized_keys en users\nombre de usuario\.ssh\. Las herramientas de OpenSSH incluyen scp (una utilidad de transferencia de archivos segura) para ayudarte con esta operación.

Para trasladar el contenido de la clave pública (~.ssh\id_ed25519.pub) a un archivo de texto denominado authorized_keys en ~.ssh\ en el servidor o host.

En este ejemplo se usa la función Repair-AuthorizedKeyPermissions en el módulo OpenSSHUtils que se instaló anteriormente en el host de las instrucciones anteriores. PowerShell

# Make sure that the .ssh directory exists in your server's home folder
ssh user1@[email protected] mkdir C:\users\user1\.ssh\

# Use scp to copy the public key file generated previously to authorized_keys on your server
scp C:\Users\user1\.ssh\id_ed25519.pub user1@[email protected]:C:\Users\user1\.ssh\authorized_keys

# Appropriately ACL the authorized_keys file on your server
ssh --% user1@[email protected] powershell -c $ConfirmPreference = 'None'; Repair-AuthorizedKeyPermission C:\Users\user1\.ssh\authorized_keys

Estos pasos completan la configuración necesaria para usar la autenticación con SSH basada en claves en Windows. Después de esto, el usuario puede conectarse al host de sshd desde cualquier cliente que tenga la clave privada.

scikit-learn: Guardar y restaurar modelos


En muchas ocasiones, mientras trabaja con la biblioteca scikit-learn , deberá guardar sus modelos de predicción en un archivo y luego restaurarlos para reutilizar su trabajo anterior para: probar su modelo con datos nuevos, comparar varios modelos o Algo más. Este procedimiento de guardado también se conoce como serialización de objetos: representa un objeto con un flujo de bytes para almacenarlo en el disco, enviarlo a través de una red o guardarlo en una base de datos, mientras que el procedimiento de restauración se conoce como deserialización. En este artículo, analizamos tres formas posibles de hacer esto en Python y scikit-learn, cada una presentada con sus pros y sus contras.

Herramientas para guardar y restaurar modelos

La primera herramienta que describimos es Pickle , la herramienta estándar de Python para la (des) serialización de objetos. Luego, miramos la biblioteca Joblib que ofrece (des) serialización fácil de objetos que contienen matrices de datos grandes, y finalmente presentamos un enfoque manual para guardar y restaurar objetos hacia / desde JSON (JavaScript Object Notation). Ninguno de estos enfoques representa una solución óptima, pero se debe elegir el ajuste correcto de acuerdo con las necesidades de su proyecto.

Inicialización del modelo

Inicialmente, creemos un modelo de scikit-learn. En nuestro ejemplo usaremos un modelo de regresión logística y el conjunto de datos Iris . 

Vamos a importar las bibliotecas necesarias, cargar los datos y dividirlos en conjuntos de prueba y entrenamiento.

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# Load and split data
data = load_iris()
Xtrain, Xtest, Ytrain, Ytest = train_test_split(data.data, data.target, test_size=0.3, random_state=4)

Ahora creemos el modelo con algunos parámetros no predeterminados y ajustémoslo a los datos de entrenamiento. Suponemos que ha encontrado previamente los parámetros óptimos del modelo, es decir, los que producen la mayor precisión estimada.

# Create a model
model = LogisticRegression(C=0.1, 
                           max_iter=20, 
                           fit_intercept=True, 
                           n_jobs=3, 
                           solver='liblinear')
model.fit(Xtrain, Ytrain)

Y nuestro modelo resultante:

LogisticRegression(C=0.1, class_weight=None, dual=False, fit_intercept=True,
    intercept_scaling=1, max_iter=20, multi_class='ovr', n_jobs=3,
    penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
    verbose=0, warm_start=False)

Usando el fitmétodo, el modelo ha aprendido sus coeficientes que están almacenados en model.coef_. El objetivo es guardar los parámetros y los coeficientes del modelo en un archivo, por lo que no es necesario repetir el entrenamiento del modelo y los pasos de optimización de los parámetros nuevamente con datos nuevos.

Módulo pickle

En las siguientes líneas de código, el modelo que creamos en el paso anterior se guarda en un archivo y luego se carga como un nuevo objeto llamado pickled_model. A continuación, el modelo cargado se utiliza para calcular la puntuación de precisión y predecir los resultados sobre nuevos datos no vistos (de prueba).

import pickle

#
# Create your model here (same as above)
#

# Save to file in the current working directory
pkl_filename = "pickle_model.pkl"
with open(pkl_filename, 'wb') as file:
    pickle.dump(model, file)

# Load from file
with open(pkl_filename, 'rb') as file:
    pickle_model = pickle.load(file)
    
# Calculate the accuracy score and predict target values
score = pickle_model.score(Xtest, Ytest)
print("Test score: {0:.2f} %".format(100 * score))
Ypredict = pickle_model.predict(Xtest)

La ejecución de este código debería generar su puntuación y guardar el modelo a través de Pickle:

$ python save_model_pickle.py
Test score: 91.11 %

Lo mejor de usar Pickle para guardar y restaurar nuestros modelos de aprendizaje es que es rápido: puede hacerlo en dos líneas de código. Es útil si ha optimizado los parámetros del modelo en los datos de entrenamiento, por lo que no necesita repetir este paso nuevamente. De todos modos, no guarda los resultados de la prueba ni ningún dato. Aún así, puede hacer esto guardando una tupla, o una lista, de varios objetos (y recuerde qué objeto va a dónde), de la siguiente manera:

tuple_objects = (model, Xtrain, Ytrain, score)

# Save tuple
pickle.dump(tuple_objects, open("tuple_model.pkl", 'wb'))

# Restore tuple
pickled_model, pickled_Xtrain, pickled_Ytrain, pickled_score = pickle.load(open("tuple_model.pkl", 'rb'))

Módulo Joblib

La biblioteca Joblib está destinada a ser un reemplazo de Pickle, para objetos que contienen datos grandes. Repetiremos el procedimiento de guardar y restaurar como con Pickle.

from sklearn.externals import joblib

# Save to file in the current working directory
joblib_file = "joblib_model.pkl"
joblib.dump(model, joblib_file)

# Load from file
joblib_model = joblib.load(joblib_file)

# Calculate the accuracy and predictions
score = joblib_model.score(Xtest, Ytest)
print("Test score: {0:.2f} %".format(100 * score))
Ypredict = pickle_model.predict(Xtest)
$ python save_model_joblib.py
Test score: 91.11 %

Como se ve en el ejemplo, la biblioteca Joblib ofrece un flujo de trabajo un poco más simple en comparación con Pickle. Si bien Pickle requiere que se pase un objeto de archivo como argumento, Joblib funciona tanto con objetos de archivo como con nombres de archivo de cadena. En caso de que su modelo contenga grandes conjuntos de datos, cada conjunto se almacenará en un archivo separado, pero el procedimiento de guardar y restaurar seguirá siendo el mismo. Joblib también permite diferentes métodos de compresión, como ‘zlib’, ‘gzip’, ‘bz2’ y diferentes niveles de compresión.

Guardar y restaurar manualmente a JSON

Dependiendo de su proyecto, muchas veces encontrará Pickle y Joblib como soluciones inadecuadas. De todos modos, siempre que desee tener un control total sobre el proceso de guardar y restaurar, la mejor manera es crear sus propias funciones manualmente.

A continuación, se muestra un ejemplo de cómo guardar y restaurar objetos manualmente mediante JSON. Este enfoque nos permite seleccionar los datos que deben guardarse, como los parámetros del modelo, los coeficientes, los datos de entrenamiento y cualquier otra cosa que necesitemos.

Dado que queremos guardar todos estos datos en un solo objeto, una forma posible de hacerlo es crear una nueva clase que herede de la clase modelo, que en nuestro ejemplo es LogisticRegression. La nueva clase, llamada MyLogReg, implementa los métodos save_jsonload_json para guardar y restaurar a / desde un archivo JSON, respectivamente.

Para simplificar, guardaremos solo tres parámetros del modelo y los datos de entrenamiento. Algunos datos adicionales que podríamos almacenar con este enfoque son, por ejemplo, una puntuación de validación cruzada en el conjunto de entrenamiento, datos de prueba, puntuación de precisión en los datos de prueba, etc.

import json
import numpy as np

class MyLogReg(LogisticRegression):
    
    # Override the class constructor
    def __init__(self, C=1.0, solver='liblinear', max_iter=100, X_train=None, Y_train=None):
        LogisticRegression.__init__(self, C=C, solver=solver, max_iter=max_iter)
        self.X_train = X_train
        self.Y_train = Y_train
        
    # A method for saving object data to JSON file
    def save_json(self, filepath):
        dict_ = {}
        dict_['C'] = self.C
        dict_['max_iter'] = self.max_iter
        dict_['solver'] = self.solver
        dict_['X_train'] = self.X_train.tolist() if self.X_train is not None else 'None'
        dict_['Y_train'] = self.Y_train.tolist() if self.Y_train is not None else 'None'
        
        # Creat json and save to file
        json_txt = json.dumps(dict_, indent=4)
        with open(filepath, 'w') as file:
            file.write(json_txt)
    
    # A method for loading data from JSON file
    def load_json(self, filepath):
        with open(filepath, 'r') as file:
            dict_ = json.load(file)
            
        self.C = dict_['C']
        self.max_iter = dict_['max_iter']
        self.solver = dict_['solver']
        self.X_train = np.asarray(dict_['X_train']) if dict_['X_train'] != 'None' else None
        self.Y_train = np.asarray(dict_['Y_train']) if dict_['Y_train'] != 'None' else None
        

Ahora probemos la MyLogRegclase. Primero creamos un objeto mylogreg, le pasamos los datos de entrenamiento y lo guardamos en un archivo. Luego creamos un nuevo objeto json_mylogregy llamamos al métod load_json para cargar los datos del archivo.

filepath = "mylogreg.json"

# Create a model and train it
mylogreg = MyLogReg(X_train=Xtrain, Y_train=Ytrain)
mylogreg.save_json(filepath)

# Create a new object and load its data from JSON file
json_mylogreg = MyLogReg()
json_mylogreg.load_json(filepath)
json_mylogreg

Al imprimir el nuevo objeto, podemos ver nuestros parámetros y datos de entrenamiento según sea necesario.

MyLogReg(C=1.0,
     X_train=array([[ 4.3,  3. ,  1.1,  0.1],
       [ 5.7,  4.4,  1.5,  0.4],
       ...,
       [ 7.2,  3. ,  5.8,  1.6],
       [ 7.7,  2.8,  6.7,  2. ]]),
     Y_train=array([0, 0, ..., 2, 2]), class_weight=None, dual=False,
     fit_intercept=True, intercept_scaling=1, max_iter=100,
     multi_class='ovr', n_jobs=1, penalty='l2', random_state=None,
     solver='liblinear', tol=0.0001, verbose=0, warm_start=False)

Dado que la serialización de datos usando JSON realmente guarda el objeto en un formato de cadena, en lugar de un flujo de bytes, el archivo ‘mylogreg.json’ podría abrirse y modificarse con un editor de texto. Aunque este enfoque sería conveniente para el desarrollador, es menos seguro ya que un intruso puede ver y modificar el contenido del archivo JSON. Además, este enfoque es más adecuado para objetos con una pequeña cantidad de variables de instancia, como los modelos scikit-learn, porque cualquier adición de nuevas variables requiere cambios en los métodos de guardar y restaurar.

Problemas de compatibilidad

Si bien algunos de los pros y los contras de cada herramienta se cubrieron en el texto hasta ahora, probablemente el mayor inconveniente de las herramientas Pickle y Joblib es su compatibilidad con diferentes modelos y versiones de Python.

Compatibilidad con la versión de Python : la documentación de ambas herramientas indica que no se recomienda (des) serializar objetos en diferentes versiones de Python, aunque podría funcionar con cambios menores de versión.

Compatibilidad del modelo : uno de los errores más frecuentes es guardar su modelo con Pickle o Joblib y luego cambiar el modelo antes de intentar restaurar desde un archivo. La estructura interna del modelo debe permanecer sin cambios entre guardar y recargar.

Un último problema con Pickle y Joblib está relacionado con la seguridad. Ambas herramientas pueden contener código malicioso, por lo que no se recomienda restaurar datos de fuentes no confiables o no autenticadas.

Conclusiones

En esta publicación describimos tres herramientas para guardar y restaurar modelos de scikit-learn. Las bibliotecas Pickle y Joblib son rápidas y fáciles de usar, pero tienen problemas de compatibilidad en diferentes versiones de Python y cambios en el modelo de aprendizaje. Por otro lado, el enfoque manual es más difícil de implementar y debe modificarse con cualquier cambio en la estructura del modelo, pero en el lado positivo podría adaptarse fácilmente a varias necesidades y no tiene problemas de compatibilidad.