TLS
avec Gateway API: Une gestion efficace et sécurisée des certificats publiques et privés
Sommaire
Le chiffrement TLS est un standard incontournable dans la sécurisation des services et applications, que ce soit sur Internet ou au sein même de l'entreprise. Sur Internet, le recours à un certificat TLS, validé par une autorité de certification reconnue, est essentiel pour garantir la confidentialité des échanges de données.
En ce qui concerne les communications internes, la PKI privée (Private Public Key Infrastructure) joue un rôle crucial dans la distribution et la validation des certificats nécessaires au chiffrement des communications au sein de l'entreprise, assurant ainsi une sécurité renforcée.
Dans cet article, nous allons plonger dans le vif du sujet : la mise en place d'une gestion efficace et robuste des certificats TLS au sein d'une entreprise. Nous explorerons les meilleures pratiques, les outils et les stratégies pour une infrastructure de certificats fiable.
🎯 Notre objectif
![](pki-gapi.png)
- Afin que les utilisateurs puissent accéder aux applications, nous utiliserons le standard Gateway API. (Je vous invite à lire mon précédent article sur le sujet.)
- Dans l'implémentation présentée ci-dessus, un composant joue un rôle majeur: Cert-manager. Il s'agit en effet du moteur central qui se chargera de générer et de renouveler les certificats.
- Pour les applications destinées à rester internes et non exposées sur Internet, nous opterons pour la génération de certificats via une PKI privée avec Vault d'Hashicorp.
- Quant aux applications publiques, elles utiliseront des certificats délivrés par Let's Encrypt.
🛂 A propos de Let's Encrypt
Basé sur le protocole ACME (Automatic Certificate Management Environment), cette solution permet une installation et un renouvellement automatiques des certificats. Let's Encrypt est simple à mettre en oeuvre, gratuit et améliore la sécurité. Cependant, il est important de noter que la durée des certificats est courte, et nécessite donc des renouvellements fréquents. Pour en savoir plus sur son fonctionnement, vous pouvez vous référer à cette documentation. | ![]() |
🔐 Une PKI privée avec Vault
Une PKI privée, ou Infrastructure à Clé Publique privée, est un système cryptographique utilisé au sein d'une organisation pour sécuriser les données et les communications. Elle repose sur une Autorité de Certification (CA) interne qui émet des certificats TLS spécifiques à l'organisation.
Ce système permet à une organisation de :
- Contrôler entièrement les procédures de vérification de l'identité et de l'authentification, et d'émettre des certificats pour des domaines internes, ce qui n'est pas possible avec Let's Encrypt.
- Sécuriser les communications et les données internes avec une authentification et un chiffrement forts au sein de l'organisation.
Cependant, la mise en œuvre de ce type d'infrastructure requiert une attention particulière et une gestion de plusieurs composants. Ici, nous allons explorer une des fonctionnalités principales de Vault, qui est initialement un outil de gestion de secrets mais qui peut aussi faire office de PKI interne.
Toutes les actions réalisées dans cet article proviennent de ce dépôt git
On y trouve le code Opentofu
permettant de déployer et configurer Vault mais aussi de nombreuses sources qui me permettent de construire mes articles de blog. N'hésitez pas à me faire des retours, ouvrir des issues si nécessaire ... 🙏
✅ Prérequis
![]() | Une infrastructure à trois niveaux de PKI (Three-tier PKI) comprend une Autorité de Certification Racine (CA) au sommet, des Autorités de Certification Intermédiaires au milieu, et des Entités Finales à la base. L'Autorité de Certification Racine délivre des certificats aux Autorités de Certification Intermédiaires, qui à leur tour émettent des certificats aux utilisateurs finaux ou aux dispositifs.Cette structure renforce la sécurité en réduisant l'exposition de l'Autorité de Certification Racine et simplifie la gestion et la révocation des certificats, offrant une solution évolutive et flexible pour la sécurité numérique. |
Pour renforcer la sécurité le système de gestion de certificats, il est recommandé de créer une Autorité de Certification Racine (AC Racine) hors ligne. Nous devons donc réaliser, au préalable, les étapes suivantes :
Générer l'Autorité de Certification Racine hors ligne : Cette approche minimise les risques de sécurité en isolant l'AC Racine du réseau.
Créer une Autorité de Certification Intermédiaire : Elle agit sous l'autorité de l'AC Racine et est utilisée pour émettre des certificats, permettant une gestion plus flexible et sécurisée.
Générer le certificat pour le serveur Vault depuis l'AC Intermédiaire : Cela assure une chaîne de confiance depuis l'AC Racine jusqu'aux certificats utilisateurs finaux, en passant par l'AC Intermédiaire.
En suivant la procédure décrite ici vous devriez obtenir les fichiers suivants qui seront utilisés dans le reste de cet article. Il s'agit là d'une proposition basé sur openssl
, et vous pouvez utiliser la méthode qui vous convient pour parvenir au même résultat
1cd terraform/vault/cluster
2
3ls .tls/*.pem
4.tls/bundle.pem .tls/ca-chain.pem .tls/intermediate-ca-key.pem .tls/intermediate-ca.pem .tls/root-ca-key.pem .tls/root-ca.pem .tls/vault-key.pem .tls/vault.pem
🏗️ Construire le cluster
Il existe plusieurs méthodes pour déployer un cluster Vault mais je n'ai pas trouvé celle qui me convenait, je l'ai donc construite en prenant les décisions suivantes:
Stockage intégré basé sur le protocole Raft, qui est particulièrement adapté aux systèmes distribués et garantit une résilience élevée. Voici un tableau illustrant la tolérance aux pannes en fonction de la taille du cluster :
Cluster size Failure tolerance 1 0 3 1 5 2 7 3 Par conséquent notre cluster Vault sera composé de 5 membres, ce qui nous permettra de tolérer la défaillance de 2 nœuds.
Stratégie de nœuds éphémères avec instances SPOT : L'architecture est constituée exclusivement d'instances SPOT pour une efficacité optimale en termes de coût. Ce groupe est configuré avec trois pools d'instances Spot distincts, chacun exploitant un type d'instance différent. Cette diversification stratégique vise à pallier toute défaillance potentielle liée à une pénurie spécifique de type d'instance SPOT, assurant ainsi une haute disponibilité et une continuité de service ininterrompue, tout en maximisant l'efficience des coûts.
Fonctionnalité de déverrouillage automatique de Vault (Unseal) : Cette fonction est essentielle compte tenu de la nature éphémère de nos nœuds. Elle permet de minimiser les temps d'arrêt et d'éliminer le besoin d'interventions manuelles pour le déverrouillage de Vault.
Cet article n'a pas pour but de décrire toutes les étapes qui sont disponibles dans la documentation du repo Github. Le fichier de variables Opentofu
contient la configuration souhaitée.
1name = "ogenki-vault"
2leader_tls_servername = "vault.priv.cloud.ogenki.io"
3domain_name = "priv.cloud.ogenki.io"
4env = "dev"
5mode = "ha"
6region = "eu-west-3"
7enable_ssm = true
8
9# Use hardened AMI
10ami_owner = "xxx" # Compte AWS où se trouve l'AMI
11ami_filter = {
12 "name" = ["*hardened-ubuntu-*"]
13}
Après avoir exécuté l'ensemble des étapes, Vault peut être utilisé et nous obtenons un cluster constitué de 5 noeuds.
🛠️ Configuration
Le déploiement d'une plateforme complète se fait par étapes distinctes car certaines opérations doivent être faites manuellement afin de garantir une sécurité optimale: La génération du certificat racine qui doit être conservé hors ligne et l'initialisation de Vault avec le token root initial.
Il faut bien entendu tous les composants réseaux afin d'y déployer des machines, puis le cluster Vault peut être installé et configuré avant de considérer l'ajout d'autres éléments d'infrastructure, qui dépendront probablement des informations sensibles stockées dans Vault.
La configuration de Vault est appliquée grâce au provider Terraform dont l'authentification se fait via un token généré depuis l'instance Vault. La proposition ici démontre comment configurer la PKI et autoriser les applications internes à interagir avec l'API de Vault et, en particulier, comment configurer Cert-Manager
.
Il suffit donc de déclarer les variables propre à votre organisation
1domain_name = "priv.cloud.ogenki.io"
2pki_common_name = "Ogenki Vault Issuer"
3pki_country = "France"
4pki_organization = "Ogenki"
5pki_domains = [
6 "cluster.local",
7 "priv.cloud.ogenki.io"
8]
Après avoir suivi la procédure, la PKI est configurée et il est alors possible de générer des certificats.
Contrairement aux PKI publiques, où les certificats sont automatiquement approuvés par les logiciels clients, dans une PKI privée, les certificats doivent être approuvés manuellement par les utilisateurs ou déployés sur tous les appareils par l'administrateur de domaine
💾 Sauvegardes planifiées
Comme toute solution contenant des données, il est indispensable de les sauvegarder. Notons aussi la sensibilité de celles stockées dans Vault. Il nous faut donc une sauvegarde régulière dans lieu sécurisé.La solution proposée ici est tout simplement une Cronjob. Elle utilise Crossplane pour construire les ressources AWS et se décompose comme suit:
- Un
bucket S3
où seront stockés les snapshots - Une
polique de rétention
pour ne conserver que les 30 dernières sauvegardes. - Le bucket est chiffré avec une
clé KMS
spécifique. - Un
external-secret
pour pouvoir récupérer les paramètres d'authentificatinon de l'Approle
spécifique à la Cronjob. - Une
Cronjob
qui exécute le script disponible dans le repo et qui éffectue un snapshot tel que décrit dans la doc d'Hashicorp. - Un rôle
IRSA
qui donne les permissions au pod d'écrire les snapshots sur S3.
🚀 En pratique avec Gateway API!
L'objectif de cet article est de démontrer une utilisation concrète avec Gateway-API
et, en fonction du protocole utilisé, plusieurs options sont possibles pour sécuriser les connexions avec du TLS. Nous pouvons notamment faire du Passthrough
et faire en sorte que la terminaison TLS se fasse sur l'upstream (exposé directement par le pod).En revanche pour notre cas d'usage, nous allons utiliser le cas le plus commun: HTTPS au niveau de la Gateway.
Voici un exemple simple car il suffit uniquement d'indiquer le secret Kubernetes contenant le certificat
1listeners:
2- protocol: HTTPS
3 port: 443
4 tls:
5 mode: Terminate
6 certificateRefs:
7 - name: foobar-tls
Voyons cela en détail, car il y a certains éléments à préparer afin de pouvoir obtenir ces secrets 🔍.
☁️ Un certificat publique
![]() | Cert-Manager est un outil open source permettant de gérer les certificats TLS dans Kubernetes.Il s'agit, en fait, d'un opérateur Kubernetes qui est contrôlé par l'usage de CRDs (Custom Resources Definitions): il est en effet possible de générer des certificats en créant des resources de type certificate. Cert-manager se charge ensuite de vérifier qu'ils sont toujours valides et déclenche un renouvellement lorsque c'est nécessaire.Il peut être intégré avec un nombre grandissant d'autorité de certifications comme Let's Encrypt, Venafi, Google, Vault ... |
Dans la mise en place de cert-manager avec Let's Encrypt, on utilise un Issuer
pour configurer la génération de certificats dans un namespace spécifique. Par contre, un ClusterIssuer
étend cette capacité à tous les namespaces du cluster, offrant ainsi une solution plus globale et flexible pour la gestion des certificats.
security/base/cert-manager/le-clusterissuer-prod.yaml
1apiVersion: cert-manager.io/v1
2kind: ClusterIssuer
3metadata:
4 name: letsencrypt-prod
5spec:
6 acme:
7 email: mymail@domain.tld
8 server: https://acme-v02.api.letsencrypt.org/directory
9 privateKeySecretRef:
10 name: ogenki-issuer-account-key
11 solvers:
12 - selector:
13 dnsZones:
14 - "cloud.ogenki.io"
15 dns01:
16 route53:
17 region: eu-west-3
- Nous utilisons ici l'instance de prod de Let's Encrypt qui est soumise à certaines règles et il est recommandé de commencer vos tests sur l'instance de staging.
- L'adresse email est utilisée pour recevoir des notifications, comme la nécessité de renouveler
- Une clé
ogenki-issuer-account-key
est générée et est utilisée pour s'authentifier auprès du serveur ACME. - Le mécanisme qui permet de prouver la légitimité d'une demande de certificat est faite grâce à une résolution DNS.
A présent, comment pouvons-nous faire appel à ce ClusterIssuer
depuis une resource Gateway-API
?Figurez-vous qu'il y a une intégration très simple par l'usage d'une annotation au niveau de la Gateway
. Cette solution est expérimentale et requiert un paramètre spécifique lors du déploiment de cert-manager.
security/base/cert-manager/helmrelease.yaml
1apiVersion: helm.toolkit.fluxcd.io/v2beta2
2kind: HelmRelease
3metadata:
4 name: cert-manager
5 namespace: security
6spec:
7 values:
8...
9 featureGates: ExperimentalGatewayAPISupport=true
Il est aussi nécessaire de donner les permissions au contrôleur Cert-manager d'interagir avec Route53 pour pouvoir compléter le challenge DNS. Ici j'utilise une Composition
Crossplane. (ℹ️ Si vous souhaitez creuser le sujet c'est par ici.)
Puis il faut ajouter l'annotation dans la Gateway et indiquer le secret cible.
infrastructure/base/gapi/platform-public-gateway.yaml
1apiVersion: gateway.networking.k8s.io/v1beta1
2kind: Gateway
3metadata:
4 name: platform-public
5 annotations:
6 cert-manager.io/cluster-issuer: letsencrypt-prod
7spec:
8 gatewayClassName: cilium
9 listeners:
10 - name: http
11 hostname: "*.${domain_name}"
12...
13 tls:
14 mode: Terminate
15 certificateRefs:
16 - name: platform-public-tls
Lorsque la Gateway est créé, un certificat est généré. Ce certificat utilise le ClusterIssuer
letsencrypt-prod indiqué ci-dessus.
1kubectl describe certificate -n infrastructure platform-public-tls
2Name: platform-public-tls
3Namespace: infrastructure
4API Version: cert-manager.io/v1
5Kind: Certificate
6...
7Spec:
8 Dns Names:
9 *.cloud.ogenki.io
10 Issuer Ref:
11 Group: cert-manager.io
12 Kind: ClusterIssuer
13 Name: letsencrypt-prod
14 Secret Name: platform-public-tls
15 Usages:
16 digital signature
17 key encipherment
18Status:
19 Conditions:
20 Last Transition Time: 2024-01-24T20:43:26Z
21 Message: Certificate is up to date and has not expired
22 Observed Generation: 1
23 Reason: Ready
24 Status: True
25 Type: Ready
26 Not After: 2024-04-23T19:43:24Z
27 Not Before: 2024-01-24T19:43:25Z
28 Renewal Time: 2024-03-24T19:43:24Z
29 Revision: 1
Enfin, au bout de quelques secondes, un secret Kubernetes est créé et contient le certificat. Il s'agit d'un secret de type TLS contenant les fichiers tls.crt
tls.key
et ca.crt
Les certificats générés par cert-manager sont stockés dans des secrets Kubernetes. Bien qu'il soit possible de les extraire à coup de commandes base64
et openssl
. Pourquoi ne pas se simplifier la vie?
Je suis un adepte de la ligne de commande et j'utilise pour ma part régulièrement le plugin view-cert qui permet d'afficher une synthèse des secrets de type tls
.
1kubectl view-cert -n infrastructure platform-public-tls
2[
3 {
4 "SecretName": "platform-public-tls",
5 "Namespace": "infrastructure",
6 "Version": 3,
7 "SerialNumber": "35f659ad03e437805fbf48111b74738efe3",
8 "Issuer": "CN=R3,O=Let's Encrypt,C=US",
9 "Validity": {
10 "NotBefore": "2024-01-28T09:41:35Z",
11 "NotAfter": "2024-04-27T09:41:34Z"
12 },
13 "Subject": "CN=*.cloud.ogenki.io",
14 "IsCA": false
15 }
16]
Il peut être installé en utilisant krew
1kubectl krew install view-cert
🏠 Un certificat privée
Pour un certificat privé avec Vault, nous devons aussi déclarer un ClusterIssuer
mais sa définition différe légerement:
security/base/cert-manager/vault-clusterissuer.yaml
1apiVersion: cert-manager.io/v1
2kind: ClusterIssuer
3metadata:
4 name: vault
5 namespace: security
6spec:
7 vault:
8 server: https://vault.priv.cloud.ogenki.io:8200
9 path: pki_private_issuer/sign/ogenki
10 caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0...
11 auth:
12 appRole:
13 path: approle
14 roleId: f8363d0f-b7db-9b08-67ab-8425ab527587
15 secretRef:
16 name: cert-manager-vault-approle
17 key: secretId
- L'URL indiquée est celle du serveur Vault. Elle doit être accessible depuis les pods dans Kubernetes
- Le
path
dans Vault fait partie de la phase de configuration de Vault. Il s'agit du rôle autorisé à généré des certificats. - Nous utilisons ici une authentification via un Approle.
Pour plus de détails sur l'ensemble des actions nécessaires à la configuration de Cert-Manager avec Vault, vous référer à cette procédure.
La principale différence avec la méthode utilisée pour Let's Encrypt réside dans le faut que le certificat doit être créé explicitement. En effet, la méthode précédente permettait de le faire automatiquement avec une annotation.
infrastructure/base/gapi/platform-private-gateway-certificate.yaml
1apiVersion: cert-manager.io/v1
2kind: Certificate
3metadata:
4 name: private-gateway-certificate
5spec:
6 secretName: private-gateway-tls
7 duration: 2160h # 90d
8 renewBefore: 360h # 15d
9 commonName: private-gateway.priv.cloud.ogenki.io
10 dnsNames:
11 - gitops-${cluster_name}.priv.${domain_name}
12 - grafana-${cluster_name}.priv.${domain_name}
13 - harbor.priv.${domain_name}
14 issuerRef:
15 name: vault
16 kind: ClusterIssuer
17 group: cert-manager.io
Comme on peut le voir, ce certificat pourra être utilisé pour accéder aux applications weave-gitops
, grafana
et harbor
. Il a une durée de validité de 90 jours et sera renouvelé automatiquement 15 jours avant son expiration.
Quelques secondes après la création de la resource certificate
, un secret Kubernetes est généré.
1kubectl describe certificates -n infrastructure private-gateway-certificate
2Name: private-gateway-certificate
3Namespace: infrastructure
4API Version: cert-manager.io/v1
5Kind: Certificate
6...
7Spec:
8 Common Name: private-gateway.priv.cloud.ogenki.io
9 Dns Names:
10 gitops-mycluster-0.priv.cloud.ogenki.io
11 grafana-mycluster-0.priv.cloud.ogenki.io
12 harbor.priv.cloud.ogenki.io
13 Duration: 2160h0m0s
14 Issuer Ref:
15 Group: cert-manager.io
16 Kind: ClusterIssuer
17 Name: vault
18 Renew Before: 360h0m0s
19 Secret Name: private-gateway-tls
20Status:
21 Conditions:
22 Last Transition Time: 2024-01-27T19:54:57Z
23 Message: Certificate is up to date and has not expired
24 Observed Generation: 1
25 Reason: Ready
26 Status: True
27 Type: Ready
28 Not After: 2024-04-26T19:54:57Z
29 Not Before: 2024-01-27T19:54:27Z
30 Renewal Time: 2024-04-11T19:54:57Z
31 Revision: 1
32Events:
33 Type Reason Age From Message
34 ---- ------ ---- ---- -------
35 Normal Issuing 41m cert-manager-certificates-trigger Issuing certificate as Secret does not exist
36 Normal Generated 41m cert-manager-certificates-key-manager Stored new private key in temporary Secret resource "private-gateway-certificate-jggkv"
37 Normal Requested 41m cert-manager-certificates-request-manager Created new CertificateRequest resource "private-gateway-certificate-1"
38 Normal Issuing 38m cert-manager-certificates-issuing The certificate has been successfully issued
Enfin, Il suffit d'utiliser ce secret dans la déclaration de la Gateway
privée.
infrastructure/base/gapi/platform-private-gateway.yaml
1apiVersion: gateway.networking.k8s.io/v1beta1
2kind: Gateway
3metadata:
4 name: platform-private
5spec:
6 gatewayClassName: cilium
7 listeners:
8 - name: http
9 hostname: "*.priv.${domain_name}"
10...
11 tls:
12 mode: Terminate
13 certificateRefs:
14 - name: private-gateway-tls
Nous pouvons vérifier l'autorité de certification en utilisant la commande curl
:
1curl --verbose -k https://gitops-mycluster-0.priv.cloud.ogenki.io 2>&1 | grep 'issuer:'
2* issuer: O=Ogenki; CN=Ogenki Vault Issuer
💭 Dernières remarques
❓ Qui n'a pas subit un incident lié au renouvellement d'un certificat?❓ Comment obtenir un niveau de sécurité correspondant aux éxigences de l'entreprise?❓ Comment peut-on se simplifier les tâches opérationnelles liées à la maintenance des certificats TLS?
Nous avons pu explorer une réponse concrète à ces questions dans cet article. L'automatisation mise en oeuvre grâce à Cert-manager permet de minimiser les tâches opérationnelles tout en améliorant le niveau de sécurité.
La mise en oeuvre avec Let's Encrypt et Gateway API
est vraiment super simple! D'autre part le niveau de sécurité apporté par Vault pour les commmunications internes est vraiment à considérer. Cependant, il est vrai que cela nécessite la mise en oeuvre de plusieurs composants et une rigueur sans faille pour conserver un niveau de sécurité optimal.
Il est important de rappeler quelques recommandations et bonnes pratiques avant de considérer une mise en production. Afin que cet article reste lisible, certains points ne sont même pas été adressés mais il est primordial de les inclure dans votre stratégie:
- Conservez le certificat racine hors ligne. En d'autres termes, il est impératif de le stocker sur un support non connecté pour le protéger de toute menace potentielle.
- La révocation de la CA root ou intermédiaire n'a pas été évoqué. Ainsi que la mise à disposition d'une liste de révocation (Certificate Revocation List).
- L'accès à l'API Vault doit être rigoureusement restreint à un réseau privé. Vous devriez jeter un coup d'oeil à mon article sur Tailscale.
- Vous noterez aussi que je ne parle pas du tout d'authentification mais Il est essentiel de configurer un fournisseur d'identité dès le début et d'activer l'authentification multi-facteurs (MFA) pour renforcer la sécurité. Par ailleurs, il est conseillé de révoquer le token racine de Vault une fois l'authentification et les autorisations adéquates mises en place. Si nécessaire, le token peut être régénéré suivant la procédure disponible ici.
- Par défaut le code proposé déploie des AMI (Images d'instances AWS) Ubuntu de Canonical. Il est conseillé d'en utiliser une dont la sécurité a été renforcée (Hardened AMI). J'ai construit la mienne en utilisant ce projet.
- Afin de pouvoir initialiser Vault une commande doit être lancée sur l'instance ce qui justifie l'utilisation de SSM. Cependant il est conseillé de le désactiver lorsque la phase d'initialisation est terminée (
enable_ssm: false
dans les variables Opentofu) - Envoyez les logs d'audit vers un SIEM afin de pouvoir détecter des comportements suspects.
- Alerter avant que les certificats n'arrivent à expiration. Vous pouvez, par exemple, utiliser cet exporter Prometheus opensourcé par les potes d'Enix 😉. Il s'agit là d'une sécurité supplémentaire sachant que l'architecture proposée rend le tout automatisé.
- Accordez une attention particulière aux clés KMS: celle utilisé pour dévérouiller Vault, mais aussi celle qui permet de créer des snapshots. Elles sont vitalespour la restauration de vos sauvegardes.
- "Une sauvegarde qui n'est pas vérifiée ne sert à rien": Il faut donc construire un workflow qui permettra de vérifier la consistence des données dans Vault. C'est peut être le sujet d'un autre article, stay tuned!
- Organisez périodiquement des exercices de reprise après sinistre (PRA) pour garantir votre capacité à reconstruire l'ensemble du système à partir de zéro, en vous assurant de disposer de toute la documentation et des outils nécessaires.
🔖 References
Github Issues
- Cert-manager et Vault: chaine complète de l'autorité de certification
Blog posts
- Privé vs Public PKI: Construire un plan efficace (Author: Nick Naziridis)
- PKI Meilleures pratiques pour 2023
- Build an Internal PKI with Vault (Author: Stéphane Este-Gracias)
Documentation Hashicorp
- A propos du stockage Raft:
- Production hardening
- PKI