Client VPN remet une adresse au client, la poignée de main se termine, et la connexion ne mène toujours nulle part. Avant de toucher aux certificats ou aux versions du client, regardez la table de routes du point de terminaison lui-même. Un point de terminaison AWS Client VPN ne transmet un paquet que lorsqu'une de ses routes couvre le préfixe de destination ; si rien ne correspond, le paquet est rejeté au point de terminaison et l'hôte cible ne voit jamais de SYN.

C'est une décision de transmission, pas une décision d'authentification. Elle intervient après la montée du tunnel et après l'évaluation des règles d'autorisation, c'est pourquoi un certificat valide et un groupe valide peuvent malgré tout produire une connexion morte.

Le point de terminaison ne transmet que ce que couvre sa table de routes

Chaque route est un CIDR de destination plus le sous-réseau qui doit recevoir le trafic. Le point de terminaison compare la destination du client à cette liste, choisit l'entrée correspondante et envoie le paquet dans le sous-réseau associé. Pas d'entrée, pas de saut suivant.

Remarquez ce que la route n'est pas. Ce n'est pas une entrée de la table de routes du VPC, et elle n'autorise rien. Les règles d'autorisation décident si un groupe peut atteindre une destination ; les routes décident si le point de terminaison a un endroit où envoyer le paquet. Les deux doivent concorder, et elles échouent avec des symptômes différents, donc deviner laquelle est cassée coûte un après-midi.

Le décalage classique est un VPC appairé. Quelqu'un ajoute une route pour 10.20.0.0/24 alors que l'hôte vit en 10.20.4.0/24, si bien que l'adresse tombe hors de chaque entrée. Une capture côté client montre des SYN répétés sans réponse, et les journaux de la cible ne montrent absolument rien.

Lisez la liste des routes avant le certificat

Les routes du point de terminaison sont un objet à part, interrogez-les donc directement au lieu de lire la table du VPC :

aws ec2 describe-client-vpn-routes --client-vpn-endpoint-id cvpn-endpoint-0123456789abcdef0 --query 'Routes[].{Dest:DestinationCidr,Target:TargetSubnet,Origin:Origin}'

Prenez l'adresse exacte et la longueur de préfixe du client qui échoue, puis vérifiez la couverture caractère par caractère. La correspondance du préfixe le plus long s'applique, donc une route plus large ne vous sauve pas quand une route plus étroite pointe ailleurs.

Une ressource par destination avec for_each

Les blocs de routes écrits à la main dérivent dès que quelqu'un ajoute un préfixe dans la console. Gardez les destinations dans une map et créez une ressource par clé, pour que chaque ajout ou suppression reste isolé.

variable "client_vpn_routes" {
  type = map(string)
  default = {
    "10.20.0.0/16"  = "subnet-0aa11bb22cc33dd44"
    "172.31.0.0/20" = "subnet-0aa11bb22cc33dd44"
  }
}

resource "aws_ec2_client_vpn_route" "this" {
  for_each               = var.client_vpn_routes
  client_vpn_endpoint_id = var.client_vpn_endpoint_id
  destination_cidr_block = each.key
  target_vpc_subnet_id   = each.value
  depends_on             = [aws_ec2_client_vpn_network_association.this]
}

Conservez la dépendance à l'association réseau. Une route a besoin d'un sous-réseau associé, donc un apply qui crée les deux doit ordonner l'association en premier, et sans cette référence Terraform est libre d'essayer la route d'abord. Lisez ensuite le plan : le nombre d'ajouts de routes doit correspondre aux préfixes ajoutés. Si le plan remplace aussi le point de terminaison, la ressource du point de terminaison est mal écrite, et l'appliquer déconnecte tous les clients connectés.

Adopter un point de terminaison que vous n'avez pas créé

La plupart des équipes héritent d'un point de terminaison construit dans la console, et c'est pourquoi aucun code de route n'existe. Ne montez pas un second point de terminaison juste pour faire entrer des routes dans Terraform.

Importez plutôt, ou passez les ids en variables et laissez les objets hérités non gérés. L'import exige le format d'identifiant que le fournisseur documente pour chaque type de ressource, qui encode le point de terminaison, le préfixe de destination et le sous-réseau cible. Copiez cette chaîne depuis la documentation de la ressource au lieu de la composer de mémoire, lancez un plan et attendez-vous à aucun changement avant d'ajouter quoi que ce soit de nouveau.

Suivez le paquet dans l'ordre où il voyage

Quand la route existe et que la connexion échoue encore, parcourez le chemin dans l'ordre au lieu de deviner à la couche supérieure.

  1. Route : un préfixe couvre la destination et pointe vers un sous-réseau associé au point de terminaison.
  2. Autorisation : une règle d'autorisation permet cette destination pour le groupe de l'utilisateur. Une route seule n'accorde jamais l'accès.
  3. Chemin de retour : la table de routes du sous-réseau cible renvoie les réponses vers le point de terminaison, et le groupe de sécurité autorise le port depuis le pool d'adresses du client.
  4. Application : ouvrez une vraie connexion TCP depuis le client, puis confirmez que les journaux de la cible montrent l'adresse source attendue du pool.

Changez un préfixe par apply, relisez ensuite la liste des routes et notez ensemble la destination, la règle qui autorise et le chemin de retour. La personne suivante qui déboguer une connexion refusée devrait partir de cet inventaire, pas d'une théorie sur les certificats.

Articles similaires