Blog | WeScale

Quicksight Amazon : sécurisez l'accès à vos données

Rédigé par Akram BLOUZA | 08/10/2020

Énoncé

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.

Éléments importants

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é.

Elément 1 :

Le service QuickSight n’est pas présent actuellement sur Paris.

Elément 2 :  

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.

Elément 3 :

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.

Solutions

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 :

  1. Réaliser un peering VPC entre le VPC déployé en Irlande et celui de Paris. Avec ce peering, il devient possible de router le trafic entre ces deux VPCs de manière privée.
  2. Avoir un proxy TCP permettant de remonter les données depuis la base de données PostgreSQL. Ici, nous avons choisi d’installer le serveur proxy HAProxy au niveau d’une instance EC2 localisée dans un sous-réseau privé.
  3. Pour que notre infrastructure soit hautement disponible, nous avons monté un équilibreur de charge réseau (NLB) permettant de remonter le flux TCP depuis les instances proxy vers QuickSight. Le NLB est aussi localisé dans un sous-réseau privé.
  4. Configuration de QuickSight pour la partie source de données.

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.

Infrastructure réseau et peering VPC

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
}

Création du proxy

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"
   }
}

Inscription au service QuickSight

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 :

  • “READER” pour accéder aux tableaux de bord.
  • “AUTHOR” pour la création d’une data source, des tableaux de bords.
  • “ADMIN”    pour l’administration du compte QuickSight, ce qui lui permet par exemple la  création d’une connexion VPC, l’achat de l'espace Spice ou la gestion des utilisateurs.

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

  1. Se connecter vers QuickSight sur https://quicksight.aws.amazon.com/sn/start depuis votre Account et dans la région d'Irlande.
  2. Ajouter une connexion vers le VPC récemment créé avec Configurer QuickSight > Organisation > Gestion VCP connexion > Rajout VPC connexion.

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 :

  • VPC_ID : le VPC localisé en Irlande hébergeant l’infrastructure proxy (NLB et  HAProxy).
  • Security group ID : Pour plus de sécurité et une connexion totalement privée, nous devrons préciser un security group. QuickSight attachera ce groupe de sécurité à l’ENI sur la région d'Irlande. Pour plus de sécurité, ce groupe de sécurité aura comme ingress uniquement les CIDRs du sous-réseau privé hébergeant l’équilibreur de charge réseau NLB et comme egress les CIDRs des serveurs QuickSight en Irlande, c’est à dire 52.210.255.224/27. Les CIDRs QuickSight de l’ensemble des régions AWS sont ici : https://docs.aws.amazon.com/quicksight/latest/user/regions.html.
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"]
   }
}
  • Subnet ID: Il s’agit d’un sous-réseau privé de la région d’Irlande. Ce sous-réseau privé héberge l’interface réseau.

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 :

  • Type de connexion : on sélectionne le type VPC connexion dans notre cas.
  • Database server : on renseigne le nom de domaine de l’équilibreur de charge réseau NLB.
  • Database name : on renseigne le nom de la base de données.
  • Port : le port de la base de données. Si vous êtes comme dans notre cas sur PostgreSQL et que vous n’avez pas changé le port par défaut, le port est 5432.
  • Username : l’utilisateur lecteur seule de la base de données a été initié au niveau user data de l’instance HAProxy.
  • Password : le mot de passe de l’utilisateur base de données.

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.

Conclusion

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.