Prenons le cas d’une infrastructure AWS qui est localisée dans la région parisienne (eu-west-3). Nous souhaitons exposer dans QuickSight une partie d’une base de données PostgreSQL RDS hébergée dans cette infrastructure. Pour mieux sécuriser son accès, la base de données RDS a été provisionnée dans des sous-réseaux privés et elle est accessible uniquement depuis les backends applicatifs.
Avant de préparer le dossier d’architecture technique, j’ai noté les points essentiels à prendre en compte pour que la solution cible soit conforme aux attentes : sécurité opérationnelle, disponibilité, résilience et souveraineté.
Le service QuickSight n’est pas présent actuellement sur Paris.
Avec QuickSight, il est possible de se connecter à une source de données présente dans un réseau public, mais aussi une source de données privée avec une connexion VPC. Dans notre cas, notre base de données PostgreSQL est localisée sur Paris et n’est pas accessible depuis internet (localisée dans des sous-réseaux privés). Aussi, comme il s’agit de données privées, nous ne souhaitons pas rendre l'accès à ces sources de données depuis internet possible via un proxy public par exemple.
Notre base de données PostgreSQL est hébergée à Paris. Ainsi, nous choisissons la région européenne d'Irlande pour configurer Amazon QuickSight. Sauf que Amazon QuickSight ne sait pas se connecter directement à une data source localisée dans une autre région.
L’architecture permettant de remédier à ces difficultés :
La connexion à une source de données depuis QuickSight se fait soit via le réseau public ou via une connexion VPC. Le VPC doit exister dans la même région que QuickSight.
Pour notre cas, nous avons choisi de souscrire à QuickSight dans la région d'Irlande.
Nous reviendrons plus en détail plus tard pour montrer comment réaliser cette connexion VPC.
Pour pouvoir récupérer les données depuis notre base de données PostgreSQL localisée dans une zone privée dans un VPC sur Paris, nous avons procédé ainsi :
Comme vous l’avez sûrement remarqué, la globalité de notre infrastructure est localisée dans une zone privée, et uniquement les personnes autorisées dans QuickSight peuvent créer des tableaux de bord à partir des données remontées depuis la base de données.
Nous supposons que l’infrastructure réseau est déjà présente : le VPC, les sous-réseaux, les NAT Gateway, l’Internet Gateway et les règles NACL.
Pour pouvoir router le trafic depuis le VPC hébergeant notre source de données localisée sur Paris, nous devons réaliser un peering VPC entre le VPC présent en région d'Irlande et celui de Paris. Le code Terraform réalisant ce peering est le suivant :
Réalisation du peering VPC entre le VPC “requester” présent en Irlande (vpc_id) à celui qui est "accepter" présent sur Paris (peer_vpc_id). Cette connexion est créée au niveau du VPC d’Irlande. Les VPCs concernés par ce peering se trouvent dans deux régions différentes (Irlande et Paris). Il est donc nécessaire dans notre cas de réaliser en premier lieu le peering entre les deux VPCs en désactivant l’”auto accept”, puis d’accepter cette connexion dans un second temps depuis le compte cible à Paris. Le code Terraform correspondant est le suivant :
resource "aws_vpc_peering_connection" "peer" {
provider = aws.requester
vpc_id = var.requester_vpc_id
peer_vpc_id = var.accepter_vpc_id
peer_owner_id = var.accepter_account_id
auto_accept = falsepeer_region = "eu-west-3"
}
resource "aws_vpc_peering_connection_accepter" "peer" {
provider = aws.accepter
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
auto_accept = true
}
resource "aws_vpc_peering_connection_accepter" "peer" {
provider = aws.accepter
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
auto_accept = true
}
Création d’une route depuis la table de routage principale du VPC en Irlande vers le CIDR du VPC de Paris.
resource "aws_route" "accepter_private_subnets_to_requester_private_subnets" {
provider = aws.accepter
route_table_id = var.accepter_private_route_table
destination_cidr_block = var.requester_vpcs_cidrs
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
}
Création d’une route depuis la table de routage principale du VPC à Paris vers le CIDR du VPC en Irlande.
resource "aws_route" "requester_private_subnets_to_accepeter_private_subnets" {
provider = aws.requester
route_table_id = var.requester_private_route_table
destination_cidr_block = var.accepter_vpcs_cidrs
vpc_peering_connection_id = aws_vpc_peering_connection.peer.id
}
1. Création d’un autoscaling group pour garantir la haute disponibilité du proxy. Ici nous renseignons aussi la configuration de démarrage (launch_configuration) qu’on détaille dans le paragraphe suivant. Comme précisé précédemment, les instances du proxy se trouvent dans des sous-réseaux privés renseignés avec la propriété vpc_zone_identifier.
resource "aws_autoscaling_group" "asg_quicksight" {
provider = aws.requester
name = "asg-${local.label_webapp}"
max_size = var.max_asg_size
min_size = var.min_asg_size
desired_capacity = var.desired_asg_capacity
health_check_type = "ELB"
force_delete = false
launch_configuration = aws_launch_configuration.lc_quicksight.name
default_cooldown = 10
health_check_grace_period = 750
termination_policies = ["OldestInstance"]
vpc_zone_identifier = [
data.terraform_remote_state.networks_plateforme_requester.outputs.subnet_private_ids[0],data.terraform_remote_state.networks_plateforme_requester.outputs.subnet_private_ids[1]
]
target_group_arns = [
aws_lb_target_group.tg_quicksight.arn,
]
}
2. Configuration de démarrage
La configuration de démarrage des instances EC2 est définie avec la ressource aws_launch_configuration. L’AMI utilisée est renseignée au niveau image_id. Quand les instances EC2 démarrent, le user_data rattaché à l’instance sera exécuté.
resource "aws_launch_configuration" "lc_quicksight" {
provider = aws.requester
name_prefix = "lc-proxy"
image_id = "data.aws_ami.proxy.id"
instance_type = var.instance_type
user_data = data.template_file.setup_data.rendered
iam_instance_profile = data.terraform_remote_state.profile.outputs.instances_profiles
key_name = local.key_name
enable_monitoring = true
security_groups = [
aws_security_group.lb_quicksight.id,
local.sg_id
]
root_block_device {
delete_on_termination = true
}
}
Le script user data est créé à partir du fichier template ./scripts/setup.tpl avec les variables passées dans le bloc vars. Les variables importantes à renseigner ici sont celles concernant la base de données : url, port, mot de passe et utilisateur.
data "template_file" "setup_data" {
template = file("./scripts/setup.tpl")
vars = {
account = var.account_id_list[var.deploy_env]
application_name = "xxx"
deploy_env = var.deploy_env
deploy_region = var.deploy_region
deploy_region_paris = var.deploy_region_paris
db_url = data.terraform_remote_state.rds.outputs.app_db_host
db_port = "5432"
db_password = data.terraform_remote_state.rds.outputs.db_password_source
db_url_without_port = trim(data.terraform_remote_state.rds.outputs.app_db_host, ":5432")
db_name = data.terraform_remote_state.rds.outputs.app_db_name
db_username = data.terraform_remote_state.rds.outputs.app_db_username
ro_username = "quicksight"
ro_password = aws_kms_ciphertext.quicksight_ro_password.ciphertext_blob
}
}
Dans le user data, on crée un utilisateur qui fait uniquement la lecture dans la base de données PostgreSQL et on démarre un conteneur HAProxy en passant en variables d’environnement les infos base de données.
#!/usr/bin/env bash
sudo apt update
sudo apt install -y docker-ce python-pip
sudo pip install --upgrade pip
sudo pip install awscli
sudo pip install awscli --upgrade
sudo aws configure set region ${deploy_region}
export ROPASSWORD=” Récupération des infos depuis
sudo aws configure set region ${deploy_region_paris}
export PGPASSWORD=$(aws kms decrypt --ciphertext-blob fileb://<(echo "${db_password}" | base64 --decode) --output text --query Plaintext --region ${deploy_region_paris} | base64 --decode)
if [[ $(psql --host=${db_url_without_port} --port=${db_port} --dbname=${db_name} --username=${db_username} -c "\du" | awk '{ print $1 }' | grep '${ro_username}' | wc -c) -ne 0 ]]
then
echo "user quicksight already exist"
else
psql --host=${db_url_without_port} --port=${db_port} --dbname=${db_name} --username=${db_username} --command "CREATE USER ${ro_username} WITH ENCRYPTED PASSWORD '$ROPASSWORD'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO ${ro_username}"
fi
unset PGPASSWORD
unset ROPASSWORD
sudo $(aws ecr get-login --registry-ids ${id_account} --no-include-email --region ${deploy_region_paris})
sudo docker pull ${id_account}.dkr.ecr.${deploy_region_paris}.amazonaws.com/digitallab:haproxy-1.7
sudo docker run -dit -p 5432:5432 -e db_url=${db_url} ${id_account}.dkr.ecr.${deploy_region_paris}.amazonaws.com/digitallab:haproxy-1.7
Dans le Dockerfile, on renseigne la version HAProxy, et on positionne le fichier de configuration haproxy.cfg pour que le proxy démarre avec la bonne configuration :
Dockerfile:
FROM haproxy:1.7
COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg
haproxy.cfg
global
daemon
maxconn 5000
defaults
mode http
timeout connect 10s
timeout client 1m
timeout server 1m
listen postg
bind 0.0.0.0:5432
mode tcp
server postg "${db_url}" check
3. Création d’un équilibreur de charge réseau : NLB
Maintenant qu’on a bien configuré le HAProxy, nous approvisionnons un équilibreur de charge réseau qui réachemine le trafic vers l’une des instances proxy up :
resource "aws_lb" "nlb_quicksight" {
provider = aws.requester
name = "nlb-quicksight"
load_balancer_type = "network"
internal = true
enable_cross_zone_load_balancing = false
enable_deletion_protection = false
subnet_mapping {
subnet_id = data.terraform_remote_state.networks_plateforme_requester.outputs.subnet_app_ids[0]
}
}
resource "aws_lb_target_group" "tg_quicksight" {
provider = aws.requester
name = "tg-quicksight"
port = 5432
protocol = "TCP"
target_type = "instance"
vpc_id = data.terraform_remote_state.networks_plateforme_requester.outputs.vpcs_ids
stickiness {
enabled = false
type = "lb_cookie"
}
health_check {
healthy_threshold = 3
unhealthy_threshold = 3
interval = 30
port = 5432
protocol = "TCP"
}
}
resource "aws_lb_listener" "lb_listener" {
provider = aws.requester
load_balancer_arn = aws_lb.nlb_quicksight.arn
port = 5432
protocol = "TCP"
default_action {
target_group_arn = aws_lb_target_group.tg_quicksight.arn
type = "forward"
}
}
Il est nécessaire de s’inscrire au service QuickSight depuis votre compte AWS. Cette inscription se fait depuis la console AWS en choisissant le service QuickSight. Une page vous demandera la validation de cette inscription :
Après la validation de cette inscription, une page s’affiche pour choisir le type d’édition QuickSight : Standard ou Entreprise. Dans notre cas, nous avons choisi l’édition Entreprise car nous avons besoin d’accéder aux données localisées dans une zone privée. L’édition Entreprise est aussi nécessaire car d’autres fonctionnalités importantes n’existent pas dans l’édition standard : rafraîchissement des données toutes les heures depuis la zone de calcul Spice, chiffrement des données en repos dans Spice et chiffrement de données en transit vers Spice. Pour accélérer la récupération des données et le calcul permettant l’affichage des tableaux de bord, QuickSight possède une moteur de calcul qui s’appelle “Spice”, utilisant une mémoire cache ayant une capacité gratuite qu’on peut augmenter jusqu'à 1 To par compte, avec une extension possible en ouvrant un ticket de support AWS. Cette page détaille l’ensemble des fonctionnalités par type d’édition, ainsi que la différence de tarification :
Enfin, une dernière page s’affiche pour saisir les informations de concernant votre compte QuickSight :
Si vous vous posez la question : est-il possible d'effectuer l’ensemble de ces étapes depuis une Infras as Code telle que terraform ? La réponse est non. Aussi, automatiser l’inscription à ce service n’est pas utile de mon point de vue car l’étape d’inscription ne se fait qu’une seule fois et vous n’avez pas besoin de plusieurs comptes QuickSight pour l’ensemble de vos données.
Par contre, il est possible avec Terraform d’automatiser la création des utilisateurs de votre compte QuickSight en utilisant la ressource aws_quicksight-user.
Un utilisateur QuickSight peut avoir trois rôles différents :
Un mail est envoyé à l’utilisateur pour confirmer son inscription, et choisir son mot de passe si le type d’identité choisi est “QUICKSIGHT” et non pas ”IAM” :
resource "aws_quicksight_user" "example" {
user_name = "akram.blouza"
email = "akram.blouza@wescale.fr"
identity_type = "IAM"
user_role = "AUTHOR"
}
Configuration QuickSight
Une fois les informations de la connexion VPC sont renseignées, QuickSight créer une Elastic Network Interface (ENI). Toutes les données transitent via cette interface réseau.
Les infos à renseigner sont :
resource "aws_security_group" "vpc_connection_quicksight" {
provider = aws.requester
name = "quicksight-vpc-connection"
description = "quicksight vpc connection security group"
vpc_id = data.terraform_remote_state.networks_plateforme_requester.outputs.vpcs_ids
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = [data.terraform_remote_state.networks_plateforme_requester.outputs.subnet_app[0].cidr_block]
}
egress {
protocol = "tcp"
from_port = 5432
to_port = 5432
cidr_blocks = ["52.210.255.224/27"]
}
}
Voici à quoi ressemble la page de configuration dans QuickSight et l'architecture AWS associée :
Une fois la connexion VPC réalisée, il est maintenant possible d'ajouter une dataset et commencer ainsi de créer vos tableaux de bord.
Pour créer la dataset il faut se placer dans l’écran d’accueil, puis choisir Datasets.
On crée une nouvelle dataset, on appuie donc sur “New dataset”.
Les éléments à renseigner pour la base de données sont les suivants :
Une fois que tous ces éléments sont renseignés, vous pourrez valider la connexion, et ainsi tout devrait être fonctionnel pour commencer à construire vos tableaux de bord dans QuickSight.
Nous avons pu détailler dans cet article l’implémentation d’un design d’infrastructure permettant d’accéder à vos données privées localisées dans la région Paris depuis un QuickSight localisé dans la région d'Irlande tout en garantissant la non exposition publique de vos données. Il est aussi important de savoir que cette problématique est bien un cas réel client, et son implémentation tourne bien dans un environnement de production. Donc si vous rencontrez cette problématique, j'espère que cet article pourra vous aider. N’hésitez pas à me poser vos questions, je ne manquerai pas d’y répondre.