Avoir un bastion SSH sur AWS gratuitement


Lorsque l’on utilise des EC2 une des bonnes pratiques d’AWS est de les mettre dans un sous-réseau privé. Pour rendre accessible un service dans l’EC2 sur internet, il faut créer un ALB dans un réseau publique et qui redirigera les requêtes vers les EC2 dans le sous-réseau privé dans le cas d’un serveur HTTP.

Le désavantage ou bonne pratique de sécurité, est qu’il n’est plus possible d’accéder en SSH aux EC2 dans le réseau privé. Il existe plusieurs options pour pouvoir y accéder de nouveaux, les 2 principales:

  1. Créer un bastion accessible depuis seulement certaine IP et qui permet de s’y connecter et de pouvoir faire des rebonds sur les EC2 dans le sous-réseau privé, cette solution n’est pas gratuite et rend la sécurité de votre infrastructure plus compliquée à gérer.

  2. Installer l’agent SSM d’AWS qui permet d’avoir un shell sur les EC2 et de pouvoir entre autre fonctionnalités de rendre disponible en local le port d’une EC2. Mais il faut que l’EC2 ai accès à internet ou aux VPC endpoints ec2messages et ssmmessages. Cette option est aussi payante. Et à quelques limitations notamment avec l’utilisation d’outils qui ont besoin de communiquer via SSH avec l’EC2 comme Ansible ou rsync.

Mon besoin

Lancer des playbooks Ansible sur plusieurs EC2 qui sont dans un sous-réseau privé à partir de mon ordinateur et par une CI/CD. En créant le moins de ressources AWS possible et avoir un coût très bas.

La solution de l’agent SSM n’a pas été retenu car :

  • Le shell obtenu par la commande aws ssm start-session ne peut pas être utilisé par Ansible
  • Le forward de port peut être utilisé mais il faut que sa clé SSH soit autorisé sur le serveur ou utiliser la keypair associé au serveur, et gérer les commandes et les ports utilisés pour les forwarder.
  • L’utilisation d’un document SSM qui serait utiliser pour télécharger le playbook Ansible depuis un S3 et l’exécuter sur l’EC2 ne correspond pas à l’usage et l’organisation que je veux avoir.

Ma solution

En parcourant les documentations d’AWS, j’ai trouvé une solution qui correspond à mon besoin de bastion SSH et en plus est gratuite.

Il s’agit de l’agent EC2 Instance Connect, son usage est assez simple il a seulement 4 commandes et nous allons en voir 3 dans cet article.

Pré-requis

Je vais utiliser des EC2 qui ont l’OS Amazon Linux et dont l’utilisateur par défaut est ec2-user

  1. Dans mon cas où les EC2 sont dans un sous-réseaux privés, il faut créer l’endpoint ec2-instance-connect dans ce sous-réseau et autorisé les EC2 à recevoir des flux SSH qui proviennent du security groups associé à l’endpoint.

  2. Installer l’agent EC2 Instance Connect sur les instances, vous pouvez suivre la documentation AWS.

  3. Il faut utiliser un rôle IAM qui autorise l’utilisation au minimum les actions suivantes, sur les VPC et EC2:

  • ec2-instance-connect:OpenTunnel
  • ec2-instance-connect:SendSSHPublicKey
  • ec2:DescribeInstances
  • ec2:DescribeVpcs

Il y a des clés de conditions qui sont disponibles pour restreindre les actions à seulement certaines ressources, utilisateurs SSH, ou tags sur les EC2. Pour plus d’informations vous pouvez regarder la documentation AWS.

Mise en pratique

Maintenant vous pouvez utiliser la commande aws ec2-instance-connect ssh pour créer un shell sur l’EC2 comme pourrait le faire SSM.

La commande aws ec2-instance-connect open-tunnel va nous permettre de seulement rediriger les flux SSH depuis notre ordinateur vers l’EC2 avec laquelle nous voulons dialoguer via SSH en passant par l’endpoint. Il n’y a pas de modification du traffic par l’endpoint de la connexion SSH effectuée. L’authentification SSH sera effectué entre votre ordinateur et l’EC2. Cette commande est l’équivalent de l’option -W du binaire ssh. Nous devons signifier au binaire SSH d’utiliser le tunnel créer par la commande via le paramètre ProxyCommand de SSH.

ssh -o ProxyCommand="aws ec2-instance-connect open-tunnel --instance-id i-xxxxxxxxxxxxxxxxx" ec2-user@<private-ip>

Vous devriez avoir la réponse suivante :

ec2-user@<private-ip>: Permission denied (publickey).

La connexion SSh n’est pas autorisé par le serveur car la clé SSH utilisée n’est pas autorisé par le serveur à se connecter. Pour résoudre ce soucis, nous allons utiliser la commande aws ec2-instance-connect send-ssh-public-key qui permet d’autoriser une clé SSH sur une EC2 pendant 60 secondes, le temps de s’y connecter.

Nous pouvons faire un script qui permet d’effectuer les 2 commandes :

# ~/ec2-proxy.sh

POSITIONAL_ARGS=()

while [[ $# -gt 0 ]]; do
  case $1 in
    -u|--user)
      USER="$2"
      shift
      shift
      ;;
    -i|--identity-file)
      IDENTITY_FILE="$2"
      shift
      shift
      ;;
    -h|--instance-id)
      INSTANCE_ID="$2"
      shift
      shift
      ;;
    *)
      POSITIONAL_ARGS+=("$1")
      shift
      ;;
  esac
done

set -- "${POSITIONAL_ARGS[@]}"

aws ec2-instance-connect send-ssh-public-key --instance-id $INSTANCE_ID --instance-os-user $USER --ssh-public-key file://$IDENTITY_FILE --no-cli-pager
aws ec2-instance-connect open-tunnel --instance-id $INSTANCE_ID

SSH permet de récupérer dynamiquement les informations de connexion comme l’utilisateur, le port, le nom d’hôte et de pouvoir les intégrer dans la commande via des caractères avec le préfix %, pour plus d’informations vous pouvez aller voir la documentation ssh_config. La commande SSH devient :

ssh -o ProxyCommand="~/proxy_ec2.sh -h %h -u %r -i ~/.ssh/id_ed25519.pub" ec2-user@i-xxxxxxxxxxxxxxxxx

Nous pouvons adapter cette configuration pour pouvoir l’utiliser dans Ansible dans le fichier d’inventaire, nous ajoutons l’option StrictHostKeyChecking=no pour avoir à éviter de valider la clé publique du serveur avant de s’y connecter.

# host.ini

[all:vars]
ansible_ssh_common_args='-o StrictHostKeyChecking=no -o ProxyCommand="~/proxy_ec2.sh -h %h -u %r -i ~/.ssh/id_ed25519.pub"'
ansible_user=ec2-user

[all]
i-xxxxxxxxxxxxxxxxx

Et maintenant, nous pouvons lancer des playbooks Ansible sur des EC2 qui sont dans un sous-réseau privé et sans coût supplémentaire.

La commande de proxy peut aussi être renseigner dans le fichier de configuration SSH pour pouvoir utiliser d’autre binaire qui utilise le SSH pour communiquer comme rsync ou se connecter en SSH plus rapidement. Si vous utilisez qu’un seul compte AWS, vous pouvez utiliser le caractère * pour ne pas avoir besoin de changer la configuration sur l’id de l’EC2 change.

# ~/.ssh/config

Host i-*
    HostName %h
    User ec2-user
    StrictHostKeyChecking no
    ProxyCommand ~/proxy_ec2.sh -h %h -u %r -i ~/.ssh/id_ed25519.pub

Et pour l’utiliser, il vous suffit d’utiliser l’id de l’instance comme alias SSH :

ssh i-xxxxxxxxxxxxxxxxx