Dans cet article nous allons mettre en place une fonction AWS Lambda pour exécuter des commandes sur une instance Linux EC2 via un appel API en HTTPS. Cette Lambda sera utile sur des environnements où les connexions SSH vers internet sont limitées ou complètement fermées.
AWS Lambda est un service de calcul à la demande (Cloud compute service) fourni par Amazon dans son catalogue de services AWS (Amazon Web Services), il a été annoncé pendant le re:Invent de novembre 2014. AWS Lambda offre la possibilité d'exécuter du code sans avoir à gérer des serveurs, c'est le concept Serverless. AWS Lambda repose sur une architecture orientée événements (Event-Driven).
Une fonction Lambda c'est une ou plusieurs portions de code représentant un programme informatique managé par le service AWS Lambda. Les ressources nécessaires pour exécuter ce code sont gérées de manière transparente pour le développeur. Le service AWS Lambda supporte les langages de programmation ci-dessous (à la date de la rédaction de cet article) :
Python
Go
Node.JS
C#
PowerShell Core
Java
L'architecture Serverless et orientée événements (Event-Driven) de AWS Lambda présente plusieurs avantages, parmi ceux-ci :
Se concentrer sur le code. Aucun serveur n'est à installer, sauvegarder, surveiller ou mettre à jour. AWS gère cette partie pour nous
Bénéficier de la haute disponibilité de la plateforme AWS
Afin de pouvoir déployer la fonction AWS Lambda que nous allons nommer ssh_execute
, les outils ci-dessous sont nécessaires :
Un compte AWS AWS Free Tier
Un éditeur de texte
Python 3.6
pip
Le module Python venv
Git
7zip
Attention, sous Windows, il est important de ne pas utiliser l'outil d'archivage fourni par défaut, car ce dernier n'est pas supporté par AWS Lambda et la fonction risque de ne pas fonctionner correctement.
Remarque : Les commandes de cet article ont été testées sur la distribution Ubuntu 18.04
Les commandes ci-dessous installent les outils nécessaires sur une distribution Ubuntu 18.04 (à exécuter en root) :
apt update
apt install python3 python3-venv python-pip p7zip-full git
git clone https://github.com/obounaim/lambda_function_examples.git
Un virtualenv (Virtual Environment) Python permet d'avoir un environnement Python unique. Il est aussi nécessaire pour inclure des bibliothèques Python qui ne sont pas fournies par défaut par l'environnement d'exécution de la fonction Lambda. Les étapes ci-dessous montrent comment le créer.
cd lambda_function_examples/execute_ssh/
python3 -m venv lambda_venv
source lambda_venv/bin/activate
Les bibliothèques sont définies dans le fichier requirements.txt
à la racine du répertoire lambda_function_examples/execute_ssh/
.
La bibliothèque "boto3" de AWS est aussi nécessaire pour le fonctionnement de notre code. Cependant, nous n'avons pas besoin de l'installer sur le nouveau virtualenv car elle est fournie par défaut par l'environnement d'exécution de la fonction Lambda.
Le gestionnaire de paquets Python pip
sera utilisé pour installer les bibliothèques.
Remarque : Le virtualenv doit être activé avant de lancer la commande pip3
pip install -r requirements.txt
Ignorer l'erreur concernant l'installation des dépendances pycparser et pynacl. Le message d'erreur "Failed building wheel" dit que pip
n'arrive pas à convertir les paquets pycparser et pynacl vers le nouveau format wheel. pip
continuera avec l'ancienne version Egg (Wheel vs Egg).
Après avoir installé les prérequis et configuré le virtualenv, nous pouvons désormais s'intéresser au code de la fonction Lambda.
lambda_function_examples/execute_ssh/
|
|-- lambda_venv
|
|-- main.py
|
|-- requirements.txt
|
|-- zip_lambda_function.sh
Le code source de la fonction AWS Lambda dans le fichier main.py est découpé en trois fonctions Python :
handler(event, context)
La fonction Python handler
est le point d'entrée de la fonction Lambda. Le service AWS Lambda invoque la fonction handler
pour lancer l'exécution du code Python.
Les paramètres event
et context
sont utilisés pour initialiser le code Python :
event : Ce paramètre, généralement de type dictionnaire permet de transmettre des paramètre concernant l’event. Dans notre cas, il contient les clés de type string
comme l'IP de l'instance EC2 le nom de l'utilisateur pour la connexion SSH, la clé privée et la commande à exécuter.
context : Ce paramètre contient des informations à propos du runtime de l’event, il faut le renseigner, mais il n’est pas utilisée dans notre cas.
connect_ssh(host_ip, host_username, encrypted_private_key)
Cette fonction se connecte en SSH sur l'IP de l'instance Linux EC2 host_ip
avec l'utilisateur host_username
et la clé privée encrypted_private_key
. L'objet retourné sera utilisé par la fonction execute_ssh_cmd()
pour exécuter des commandes sur l'instance Linux EC2.
Authentification sur l'instance Linux EC2 :
Par défaut, sur une instance Linux EC2 l'authentification se fait uniquement par une paire de clé publique/privée SSH. La clé publique doit être copiée dans le fichier .ssh/authorized_keys
dans le répertoire home de l'utilisateur utilisé pour se connecter à l'instance Linux EC2. La clé privée est utilisée pour s'authentifier sur l'instance Linux EC2. Méthode générique pour configurer une authentification SSH
Pour protéger votre clé privée, vous pouvez utiliser le service AWS KMS et la chiffrer avant de la passer à votre fonction Lambda. Je ne détaille pas la création de votre clé KMS dans cet article.
La sortie de la commande ci-dessous doit être passée à la fonction Lambda dans la clé encrypted_private_key
de la variable de type dict
event
de la fonction handler.
PATH représente le chemin vers la clé privée SSH. KEY-ID représente l'ID de la clé CMK.
aws kms encrypt --key-id KEY-ID --plaintext fileb://PATH --output text --query CiphertextBlob
Remarque: La fonction Lambda doit avoir les droits IAM nécessaires pour déchiffrer la clé SSH privée l'aide de la clé CMK
execute_ssh_cmd(ssh_client, cmd)
Cette fonction utilise l'objet retourné par la fonction connect_ssh()
pour lancer la commande cmd
.
Créer une archive zip qui contient le code Python ainsi que les bibliothèques Python nécessaires.
bash zip_lambda_function.sh
Créer la fonction Lambda et importer le fichier zip
Créer une nouvelle fonction Lambda et importer le fichier zip sur la console web AWS
Configurer la fonction Lambda dans un subnet du VPC de l'instance EC2. Le subnet doit être privé avec une route vers la NAT gateway afin que la fonction Lambda puisse invoquer l'API publique de AWS KMS
(Une alternative à la NAT Gateway est possible en utilsant VPC KMS Endpoint).
Configurer les Security Groups
de la fonction Lambda pour autoriser la connexion SSH vers l'instance EC2
Configurer un rôle IAM pour autoriser l'accès à la clé CMK sur le service KMS pour déchiffrer la clé SSH privée.
Lancer un test avec ce paramètre event
(en reiseignant vos propres informations) :
{
"username": "",
"ip": "",
"encrypted_private_key" : ""
"cmd": "date"
}
La fonction Lambda présentée peut être étendue pour servir de base pour les scénarios suivants :
Configurer un nouveau serveur en lançant un Playbook ansible
Récupérer des fichiers (Logs d'accès, images, ...) et les stocker sur S3
Lancer une tâche d'administration (Redémarrer un service, vider un cache, ...)
Lancer une tâche récurrente sur un ensemble d'instances Linux EC2 à partir d'un point central
Il est aussi important de revenir sur la criticité de la gestion de la clé ssh privée et des mots de passe d'une manière générale. L'utilisation d'une clé KMS pour chiffrer les secrets est fortement recommandée, passer des secrets en clair présente un risque de sécurité non-négligeable.
Pour plus d'informations sur le service AWS Lambda :