AWS - proxy DNS - Comment faire causer vos EC2 avec vos services legacy

Abordons dans cet article une problématique OPS relativement simple au départ : la résolution des noms DNS mais appliquée dans le cadre d'une infrastructure legacy ayant une extension sur AWS.

1. Introduction

Suite à la mise en oeuvre d'un MVP sur AWS pour l'un de nos clients, nous devions faire évoluer l'architecture pour permettre d'interroger des services legacy hébergés chez le client, et inversément, permettre à des services côté client d'interroger des services hébergés chez AWS.

La 1ere étape consiste donc à relier l'infrastructure AWS à celle du client. Pour cela, plusieurs choix sont possibles : un lien directConnect ou une connection VPN de type peer-to-peer. Pour des raisons de délai et de coûts, nous avons choisi de partir sur un bon vieux VPN.
AWS fournit évidemment un service Managé pour cela. La documentation est ici.

La mise en oeuvre du VPN fera l'objet d'un prochain article et ne sera donc pas abordé dans les détails ici. Présentons simplement ici un schéma d'architecture représentant l'état final une fois les 2 infrastructures reliées :

blog---proxy-DNS

Une fois cette 1ere étape terminée, on a donc le moyen de faire communiquer nos composants hébergés dans notre VPC. Une matrice de flux validée plus tard (et mise en oeuvre des 2 côtés du VPN), on va pouvoir commencer à interroger dans les 2 directions : C'est ici que la notion de "DNS" va intervenir.

Avant de rentrer dans les détails, commençons par un bref aperçu d'une solution "quick & dirty" :

Pour commencer rapidement, on peut modifier le fichier /etc/hosts de chaque machine pour simuler un nom d'un service.

De même, si vous utilisez des micro-services, déployés dans ECS (Elastic Container Service) vous pouvez également utiliser les extra_hosts : ceux-ci seront propagés automatiquement au niveau de vos conteneurs.

Dans la console, cela se trouve ici :

Editer une nouvelle revision de la "Task Definition" de l'un de vos microservices. Dans l'écran, dans la zone "Container Definition", cliquez sur votre container. Cela ouvre la fenêtre de modification des paramètres de votre container. Dans la zone "Network Settings", vous pouvez renseignez vos extra-hosts.

ECS-Extra-Hosts

A noter également que l'on peut s'affranchir de le faire manuellement pour chacun des micro-services en l'automatisant via un script terraform par exemple. De même, pour maintenir les entrées de vos fichiers /etc/hosts, vous pouvez utiliser ansible ou puppet pour vous faciliter la vie.

Mais cette approche n'est pas satisfaisante et pose problème.
D'abord parce qu'il faut connaître à l'avance le nom DNS des services avant de pouvoir les définir. D'autre part, si le service est accessible via plusieurs IP (round-robin au niveau du DNS), vous ne pourrez en référencer qu'une seule, ce qui cassera le côté "haute-disponibilité" du service appelé.

La solution la plus propre et la plus sécurisée est de monter un proxy DNS. Ce proxy DNS va servir dans les 2 sens de communication :

  • depuis l'infrastructure AWS, il permettra de résoudre classiquement les noms des services legacy (par exemple, monservice.acme.internal).
  • depuis l'infrastructure du client, en lui délégant une zone du DNS (par exemple *.mvp.acme.aws) il permettra de résoudre chez le client des noms en monservice.mvp.acme.aws

2. Installation du proxy DNS

Installer un proxy DNS, revient à installer les paquets bind et bind-utils sur une machine ec2. Soit 10.0.0.60 l'adresse IP de cette machine.

sudo yum install bind bind-utils

On va modifier le fichier /etc/named.conf comme suit :

// allow all internal IP Address from VPC Subnet
acl "trusted" {
  10.0.0.0/24;
  localhost;
};

options {	
    
  // start listening on local interface and IP Address
  listen-on port 53 { 127.0.0.1; 10.0.0.60; };
  
  // we do not want listening for ipv6
  // listen-on-v6 port 53 { ::1; };
    
  directory 	"/var/named";
  dump-file 	"/var/named/data/cache_dump.db";
  statistics-file "/var/named/data/named_stats.txt";
  memstatistics-file "/var/named/data/named_mem_stats.txt";
  allow-query     { trusted; };
  recursion yes;
  dnssec-enable no;
  dnssec-validation no;
};

logging {
  channel default_debug {
    file "data/named.run";
    severity dynamic;
  };
};

zone "." IN {
  type hint;
  file "named.ca";
};

include "/etc/named.rfc1912.zones";
include "/etc/named.root.key";
include "/etc/named/named.conf.local";

Le fichier /etc/named/named.conf.local va nous permettre de définir les domaines du client que l'on veut pouvoir résoudre. Pour résoudre des noms côté "client" en acme.internal, on va simplement interroger le DNS du client.
Soit 192.168.0.30 et 192.168.0.31, 2 serveurs DNS côté client. Pour que cela fonctionne, il faudra évidemment avoir autorisé au niveau des firewalls,
10.0.60.0/32 vers 192.168.0.30/32, port 53
10.0.60.0/32 vers 192.168.0.31/32, port 53

Le fichier /etc/named/named.conf.local va ressembler à ceci :

// permet de résoudre les noms en *.acme.internal
// AWS -> service Legacy ACME
zone "acme.internal" {
  type forward;
  forward only;
  forwarders { 192.168.0.30; 192.168.0.31; };
};

// générer par AWS. Correspond aux HostedZones dans route53
// on utilise le DNS AWS
zone "ippon-hosting.com" {
  type forward;
  forward only;
  forwarders { 10.0.0.2; };
};

// permet de répondre aux requêtes provenant du client en "*.mvp.acme.aws"
// Legacy ACME -> AWS
zone "mvp.acme.aws" { 
  type master; 
  file "/etc/named/zones/mvp.acme.aws"; 
  allow-transfer { any; }; 
  allow-query { any; };
};

La dernière "zone" en mvp.acme.aws sera décrite dans le §4.

Remarque : dans notre exemple, la machine ec2 sur laquelle est installée le proxy DNS a été créée dans le subnet privé contenant les autres machines ec2 "business".
On peut faire encore plus sécurisé (certains diront "paranos") en créant le proxy DNS dans un subnet dédié. Bien évidemment, il faudra adapter les règles de firewall en conséquence.
Notons néanmoins qu'il est déconseillé de mettre votre proxyDNS dans votre public subnet.

3. Utilisation de notre proxy DNS dans nos machines EC2

Pour résoudre les noms DNS, les machines EC2 utilisent un resolver dont la configuration est disponible dans /etc/resolv.conf.

Dans une infrastructure AWS, sur un subnet donné, 5 adresses sont réservées. Par exemple, pour un sous-réseau avec CIDR en 10.0.0.0/24, on aura :

  • 10.0.0.0 : correspond au réseau
  • 10.0.0.1 : Réservé pour le Routeur du VPC
  • 10.0.0.2 : Correspond au DNS AWS. Il est toujours calculé en prenant l'adresse du réseau + 2.
  • 10.0.0.3 : Réservé par AWS pour des besoins futurs
  • 10.0.0.255 : adresse de broadcast. Les VPC AWS ne supportent pas le broadcast, l'adresse est reservée pour ne pas porter à confusion.

Des explications plus détaillées sont disponibles ici

Par défaut, lorsqu'une machine EC2 est créée dans un "non default" VPC, celle-ci est référencée dans le domaine .compute.internal. Et c'est le DNS 10.0.0.2 qui va permettre de résoudre ces noms DNS.

De même, si vous définissez des "HostedZones" dans route53, on utilisera le DNS 10.0.0.2 pour résoudre les noms relatifs à ces "HostedZones".

Le fichier /etc/resolv.conf ressemble à ceci :

options timeout:2 attempts:5
; generated by /sbin/dhclient-script
search compute.internal
nameserver 10.0.0.2

Cela est visible dans la console. Sélectionner le service "VPC" et dans la liste à gauche, cliquez sur "DHCP Option Set" :

VPC-DHCP-Options-Sets-Default

Pour permettre à nos machines EC2 de résoudre des noms en "acme.internal", on va créer un nouveau "DHCP Options Set" pour notre réseau VPC.

VPC-DHCP-Options-Sets-My

Une fois cela fait, quelle est la suite ?

  • Pour toutes les machines ec2 déjà créées, il va falloir faire un "refresh" de la configuration. Soit vous lancez un reboot des machines (avec la cli aws cela se fait assez facilement), soit si le reboot n'est pas possible, vous vous connectez sur la machine et vous lancez sudo /sbin/dhclient-script

  • Pour les nouvelles machines ec2 qui seront créées dans ce VPC, il n'y a rien à faire.

Dans tous les cas, le fichier /etc/resolv.conf va ressembler à ceci :

options timeout:2 attempts:5
; generated by /sbin/dhclient-script
search acme.internal
nameserver 10.0.0.60
nameserver 10.0.0.2

L'utilisation des DNS dans une infrastructure AWS est décrite ici

4. Utilisation de notre proxy DNS pour résoudre depuis le client

Comme dit précédemment, côté infrastructure legacy, l'équipe de PROD du client va donner délégation d'une zone de leur DNS à notre proxy DNS.
Par exemple la zone mvp.acme.aws sera réservée aux services hébergés dans notre infrastructure AWS.
Pour que cela fonctionne, il faudra évidemment ouvrir les flux suivants (DNS Legacy vers Proxy DNS) :
192.168.0.30/32 vers 10.0.0.60/32, port 53
192.168.0.31/32 vers 10.0.0.60/32, port 53

Une fois les flux ouverts, dans la configuration de notre proxy DNS, dans le fichier /etc/named/named.conf.local, on va mettre ceci :

// permet de répondre aux requêtes provenant du client en "*.mvp.acme.aws"
// Legacy ACME -> AWS
zone "mvp.acme.aws" { 
   type master; 
   file "/etc/named/zones/mvp.acme.aws"; 
   allow-transfer { any; }; 
   allow-query { any; };
};

Le fichier /etc/named/zones/mvp.acme.aws va contenir les noms DNS que l'on souhaite exposer. Voici un exemple :

$TTL    3600
@ IN SOA ns1.mvp.acme.aws. support.ippon-hosting.com.  (
        2018062801 ; Serial  -> N° de série à incrémenter à chaque modif
                   ;            de ce fichier. Ce N° est utilisé par les
                   ;            serveurs esclaves pour lui indiquer qu'il
                   ;            doit mettre à jour sa base. Par commodité
                   ;            ce n° est une date à l'envers.
        10800     ;Refresh ->  A l'expiration du délai Refresh exprimé en
                   ;            secondes, le serveur escalve va entrer en
                   ;            communication avec le maitre et si il ne
                   ;            le trouve pas, il fera une nouvelle
                   ;            tentative au bout du délai Retry et si au
                   ;            bout du délai Expire il considerera que le
                   ;            serveur n'est plus disponible.
        3600      ; Retry
        604800    ; Expire
        10800 )   ; Minimum -> Durée de vie minimum du cache en secondes
		IN	NS	ns1
                IN  TXT     "v=spf1 mx -all"
                IN  SPF     "v=spf1 mx -all"
ns1		IN	A	10.0.0.60

monservice	IN	CNAME	dualstack.internal-monservice-582960992.eu-west-1.elb.amazonaws.com.

monservice2	IN	CNAME	dualstack.internal-monservice2-1032169372.eu-west-1.elb.amazonaws.com.

Conclusion

Finalement, la mise en oeuvre est assez simple.
Vous aurez peut-être noté que l'on a pas donné la procédure pour "mettre en place une délégation d'une zone DNS". Il existe pas mal de littérature concernant ce point.
DNS Windows : https://simpledns.com/kb/64/how-to-delegate-a-sub-domain-to-other-dns-servers
DNS Linux : http://www.zytrax.com/books/dns/ch9/delegate.html

Vous pourrez également noter que la notion de Haute-Disponibilité n'est pas abordée. Voici une alternative proposée par AWS qui utilise une autre implémentation d'un serveur DNS :
https://aws.amazon.com/fr/blogs/security/how-to-set-up-dns-resolution-between-on-premises-networks-and-aws-by-using-unbound/