J'utilise AWS CloudShell depuis la Console depuis un moment. C'est pratique : un shell pré-authentifié dans votre navigateur, directement dans la Console AWS. Mais je me suis toujours demandé : pourquoi je ne peux pas l'utiliser depuis mon terminal ? Pourquoi n'y a-t-il pas de commande aws cloudshell ?
Il s'avère que c'est possible. L'API existe, elle n'est simplement pas publique. Et une fois que vous avez accès à CloudShell en CLI, vous pouvez faire des choses intéressantes avec, comme utiliser un CloudShell attaché à un VPC comme bastion pour atteindre vos instances RDS privées.
Consultez le dépôt compagnon en lisant cet article.
CloudShell : une API non documentée
AWS CloudShell n'a pas de support officiel SDK ou CLI. Mais la Console doit bien communiquer avec quelque chose, non ? En regardant ce que fait le navigateur quand vous ouvrez CloudShell, vous pouvez rétro-ingénierer l'API.
Heureusement, Jérôme Guyon a déjà fait ce travail et publié un modèle de service compatible boto3. Son travail a rendu tout cela possible.
L'API est simple : créer des environnements, les démarrer/arrêter, créer des sessions, uploader/télécharger des fichiers. Le mécanisme de session utilise le protocole WebSocket de SSM sous le capot, ce qui signifie que session-manager-plugin (le même binaire qui fait tourner aws ssm start-session) peut se connecter aux sessions CloudShell.
Apprendre un nouveau tour à l'AWS CLI
L'AWS CLI a une fonctionnalité peu connue : aws configure add-model. Donnez-lui un modèle de service JSON, et soudain la CLI connaît un nouveau service. AWS utilise ça en interne pour les previews privées.
(Le modèle boto3 du dépôt de Jérôme a juste besoin d'un champ "version": "2.0" ajouté au niveau racine pour devenir compatible CLI.)
Exécutez :
aws configure add-model \
--service-model file://cloudshell-cli-model.json \
--service-name cloudshell
C'est tout. Maintenant j'ai aws cloudshell avec l'auto-complétion et tout :
$ aws cloudshell help
AVAILABLE COMMANDS
create-environment
create-session
delete-environment
describe-environments
get-environment-status
start-environment
stop-environment
...
Se connecter à CloudShell depuis le terminal
Le workflow est simple :
# Créer ou trouver un environnement
aws cloudshell create-environment --region eu-west-1
# Attendre qu'il soit RUNNING
aws cloudshell get-environment-status --environment-id <ID> --region eu-west-1
# Créer une session et se connecter
session-manager-plugin "$(aws cloudshell create-session \
--environment-id <ID> \
--session-type TMUX \
--tab-id "$(uuidgen | tr '[:upper:]' '[:lower:]')" \
--q-cli-disabled \
--region eu-west-1 \
--query '{SessionId:SessionId,TokenValue:TokenValue,StreamUrl:StreamUrl}' \
--output json)" eu-west-1 StartSession
Et vous y êtes. Un shell complet sur une instance CloudShell, depuis votre terminal. Pas besoin de navigateur.
Le problème des credentials
Il y a un hic. Quand vous utilisez CloudShell depuis la Console, AWS injecte vos credentials automatiquement via un appel API PutCredentials. Celui-ci utilise votre token de session console (l'auth par cookie de votre connexion navigateur) pour alimenter le endpoint de métadonnées du conteneur en credentials temporaires.
Quand vous vous connectez par programme, ça ne se fait pas. Le endpoint de credentials du conteneur renvoie une erreur 500. Vous devez injecter les credentials vous-même :
# Exécutez localement, puis collez la sortie dans votre session CloudShell
aws configure export-credentials --profile my-profile --format env
Pas idéal, mais ça fonctionne.
Le cas d'usage bastion
C'est là que ça devient intéressant. Vous pouvez créer un environnement CloudShell attaché à un VPC :
aws cloudshell create-environment \
--environment-name db-access \
--vpc-config '{
"VpcId": "vpc-abc123",
"SubnetIds": ["subnet-private-1"],
"SecurityGroupIds": ["sg-allowed-by-rds"]
}' \
--region eu-west-1
Mettez-le dans le même security group que celui autorisé par votre RDS, et soudain vous pouvez vous connecter à votre base de données directement depuis le shell :
mysql -h my-instance.xxx.eu-west-1.rds.amazonaws.com -u admin -p
Pas d'instance EC2 bastion à maintenir. Pas de clés SSH à gérer. Pas de coût horaire quand vous ne l'utilisez pas (CloudShell est gratuit). L'environnement se suspend après 20 minutes d'inactivité et vous pouvez le maintenir en vie avec aws cloudshell send-heart-beat.
Ce qui ne marche pas (et j'ai essayé..)
J'ai passé pas mal de temps à essayer de faire fonctionner CloudShell comme un vrai bastion de port-forwarding, pour pouvoir utiliser des outils locaux comme DBeaver contre un RDS distant à travers lui. Voici ce que j'ai trouvé :
Le port forwarding basé sur SSM ne fonctionne pas.
ECS, par exemple, enregistre les conteneurs comme cibles SSM. Son identifiant SSM n'est pas documenté mais une fois qu'on le connaît, ça marche bien, comme je l'ai décrit dans un précédent article. De cette façon vous pouvez lancer aws ssm start-session --document-name AWS-StartPortForwardingSessionToRemoteHost.
Les notebooks SageMaker ont un comportement similaire.
Les instances/conteneurs CloudShell ne semblent pas être enregistrés comme instances managées SSM. Ou s'ils le sont, c'est caché et à ce jour, personne chez AWS n'a divulgué le format de leur ID :) J'ai essayé toutes les combinaisons d'ID d'environnement, d'ID de session et de format de préfixe auxquelles j'ai pu penser. Aucune ne fonctionne.
Le port forwarding local à travers le PTY ne fonctionne pas non plus. La session est un terminal, pas un flux TCP brut. Vous ne pouvez pas faire passer des données binaires du protocole MySQL à travers. J'ai même essayé de mettre en place un relais ncat à l'intérieur de CloudShell et de tunneler à travers la session. Le relais fonctionne bien en interne, mais il n'y a aucun moyen de l'exposer comme un port TCP local sur votre machine.
Le hole punching UDP est théoriquement possible mais nécessite que le CloudShell ait accès à internet (NAT Gateway sur son subnet), et même là vous vous battez contre des problèmes de symétrie NAT des deux côtés. J'ai réussi à faire fonctionner STUN depuis CloudShell, mais le hole punch complet est fragile et impraticable pour un usage en production.
Alors à quoi ça sert ?
Honnêtement, à pas mal de choses :
- Accès rapide à la base de données sans maintenir une instance EC2 bastion. Connectez-vous, exécutez vos requêtes, déconnectez-vous. Gratuit.
-
Automatisation. Vous pouvez scripter l'exécution de commandes sur CloudShell via Python +
session-manager-plugin. Utile pour exécuter des choses à l'intérieur d'un VPC sans déployer une Lambda ou une tâche Fargate. - Débogage de connectivité réseau. Lancez un CloudShell dans une combinaison subnet/SG spécifique et testez ce qui peut atteindre quoi.
-
Transfert de fichiers (depuis les environnements publics). Les APIs
get-file-upload-urlsetget-file-download-urlsvous donnent des URLs S3 présignées.
La limitation principale est que vous êtes limité à exécuter des commandes à l'intérieur du shell. Vous ne pouvez pas l'utiliser comme un tunnel transparent pour vos outils locaux. Pour ça, vous avez toujours besoin d'une instance EC2 avec l'agent SSM, ou d'une tâche ECS avec execute-command activé.
Essayez vous-même
J'ai publié le modèle et un script d'exemple ici : github.com/psantus/cloudshell-cli
L'installation se fait en une commande. Le tout est un seul fichier JSON qui apprend un nouveau service à votre AWS CLI. Rappelez-vous juste : c'est une API non documentée. AWS peut la modifier ou la casser à tout moment. Ne construisez rien de critique dessus.
Mais pour un accès VPC rapide depuis votre terminal ? C'est plutôt génial.


