Quelques mots pour commencer#
J’ai choisi, au sein de cet article, de vous partager le code d’infrastructure que j’utilise pour configurer mes services à travers Cloudflare, c’est évidemment le cas de ce blog qui bénéficie de la couche de sécurité et de performance du réseau.
Pour ajouter un peu de piment à cet article, j’ai décidé de présenter et d’utiliser OpenTofu comme outil principal pour déployer les composants dont j’ai besoin.
Bien que le code reste en grande partie compatible avec Terraform, c’est aussi l’opportunité de quitter le mode bac à sable et d’utiliser un exemple concret pour vous parler des nouvelles fonctionnalités du langage.
Vous êtes prêt ? Allons-y !
OpenTofu, la nouvelle référence de l’infrastructure as code !#
Avez-vous déjà entendu parler d’OpenTofu ? Certainement brièvement avec mon article sur les bonnes pratiques liées à l’infrastructure as code et plus précisément à Terraform.
Ce fork de Terraform a été créé pour répondre à une inquiétude de la communauté open-source suite au changement de licence de l’outil par HashiCorp. Dans un premier temps appelé OpenTF puis OpenTofu, il est maintenant en version 1.8.1 et commence à être largement adopté que ce soit du côté des éditeurs comme Atlantis ou encore Terragrunt.
OpenTofu et Terraform restent aujourd’hui encore globalement compatibles, quelques différences se font apparaître notamment dans le comportement de certains blocs, c’est le cas de removed
. Pas d’inquiétude à avoir cependant, la documentation peut vous guider si vous souhaitez faire le pas et quitter Terraform.
Sans faire de généralité, votre code pourra être facilement migré vers OpenTofu. La seule grosse manipulation réside dans le fait d’utiliser le binaire tofu
en lieu et place de terraform
. De plus, il sera nécessaire de tester la commande plan
pour vous assurer qu’il n’y a pas de changements non désirés.
La grande force d’OpenTofu réside dans le fait d’être totalement guidé par la communauté que ce soit dans le maintien et l’amélioration du code mais aussi dans la mise en place de nouvelles fonctionnalités.
Une fonctionnalité qui a toujours été très demandée est le chiffrement du State. C’est chose faite à partir de la version 1.7.0 d’OpenTofu qui rend possible son initialisation via plusieurs blocs de configuration à mettre en place. À noter que le plan généré de la commande tofu plan
peut également être chiffré lui aussi.
Quant à la version 1.8.0, elle relève un vieux défi, le fait de mettre des variables dans les blocs backend
ou encore dans le paramètre source
des modules. De plus, il est possible de mocker les providers ou surcharger les ressources pour améliorer la fiabilité de vos tests et couvrir plus globalement votre code.
Dernier point, OpenTofu devient compatible avec les fichiers .tofu
tout en gardant la compatibilité avec les .tf
. Pour un même nom de fichier en .tf
et .tofu
, OpenTofu prendra en priorité le fichier .tofu
, ce qui permet de conserver votre code Terraform initial en surchargeant avec les dernières fonctionnalités d’OpenTofu.
Tout n’est pas encore parfait notamment au niveau de la signature avec les clés GPG de certains providers, mais cela sera de l’histoire ancienne avec l’aide de la communauté.
Vous l’aurez compris, OpenTofu se destine à être un sérieux candidat pour l’avenir de l’infrastructure as code. Il est supporté par la Linux Foundation, ce qui lui donne un argument de taille pour sa prospérité et son adoption dans le monde de l’open-source.
Sans hésiter, je pense qu’il devient l’outil à utiliser de préférence pour les nouveaux projets d’infrastructure as code, et dans un deuxième temps, commencer à migrer les anciens ! À vos marques… prêt ? Migrez !
Enfin, pour plus d’informations, je vous recommande l’écoute de cet épisode du Kubernetes Podcast de Google. Dans ce dernier, Kaslin Fields interview Ohad Maislish qui a participé aux fondations d’OpenTofu, il raconte l’histoire de la création de l’outil et l’engouement de la communauté pour ce dernier. J’ai beaucoup aimé cette interview, ce qui m’a donné encore plus envie de relever le challenge avec OpenTofu !
Cloudflare#
Protégez les services que vous hébergez#
Cloudflare fait partie des plus grands réseaux du monde où il est possible de bénéficier de fonctionnalités très pratiques comme le réseau de diffusion de contenu (CDN), la protection anti-DDoS, un DNS extrêmement rapide ainsi que la possibilité d’héberger du contenu statique grâce à son offre gratuite !
Vous pouvez obtenir des fonctionnalités avancées avec les offres Pro, Business ou encore Entreprise. Pour ça, je vous laisse consulter cette page si vous souhaitez en savoir plus.
Votre seul pré requis sera de créer un compte et de déléguer un ou plusieurs noms de domaine à Cloudflare pour l’administrer.
Pour celles et ceux qui souhaitent se lancer, l’ensemble des étapes sont détaillées avec précision ici, ce qui vous donnera différentes informations sur les capacités de la plateforme.
Personnellement, j’héberge moi-même plusieurs types de service et il est souvent essentiel de pouvoir bénéficier de certains composants comme le Web Application Firewall (WAF) pour limiter les attaques courantes quand on expose des applications sur internet.
Au-delà du portail web, Cloudflare fourni une documentation API très complète pour adresser vos besoins. J’y reviendrai un peu plus tard…
En plus de ça, et je pense que vous avez deviné, un provider officiel est également disponible pour Terraform et OpenTofu.
L’infrastructure as code dans tout ça#
Bien que Cloudflare possède son propre provider, il y a parfois des ressources qui ne sont pas disponibles. J’ai été confronté plusieurs fois à ce problème avec la partie mTLS, j’en parle un peu plus loin.
Comme dit plus haut, Cloudflare possède une bibliothèque d’APIs extrêmement riche, c’est pourquoi, lorsque la ressource n’existe pas dans le provider officiel, je me suis tourné vers le provider restapi de Mastercard. À travers l’unique ressource restapi_object
, il est possible de configurer les chemins et méthodes APIs pour ainsi définir leurs comportements à la création, suppression, voire mise à jour du service que vous souhaitez utiliser.
C’est vraiment très pratique pour mon cas d’utilisation et très simple à mettre en place quand la documentation côté APIs est riche au niveau des paramètres, méthodes et codes HTTP, etc.
Sans en dire trop, j’utilise aussi d’autres providers comme hashicorp/local et hashicorp/tls pour générer des certificats TLS et chilicat/pkcs12 afin de créer un fichier .p12 permettant de me connecter, après configuration, à mes services protégés par du mTLS.
Fonctionnalités et services#
L’offre gratuite de Cloudflare est une mine d’or pour l’hébergement de sites ou outils personnels, avec la possibilité d’aller assez loin dans la protection et l’amélioration des performances de ce que vous exposez.
L’objectif de cette section est de vous détailler l’ensemble des fonctionnalités de Cloudflare que j’utilise et que vous retrouverez “as code” un peu plus loin dans l’article.
Domain Name System (DNS)#
Chose assez classique, vous pouvez gérer vos enregistrements DNS au sein de Cloudflare, mais ça ne s’arrête pas là ! Petite particularité, Cloudflare utilise un service proxy pour améliorer la sécurité et la performance des sites web.
En tant que proxy, Cloudflare agit comme un intermédiaire entre les utilisateurs qui visitent vos sites et les services que vous exposez. Cela permet de filtrer le trafic, de bloquer les menaces, mais surtout de cacher l’adresse IP réelle du serveur d’origine, offrant ainsi une couche supplémentaire de protection.
En plus de l’aspect sécurité, Cloudflare met en cache le contenu statique, ce qui accélère le temps de chargement des pages.
Cette option de proxy est désactivable avec une granularité assez fine : l’enregistrement DNS.
Web Application Firewall (WAF)#
Si vous avez choisi d’activer l’option de proxy DNS au-dessus, vous allez pouvoir bénéficier du WAF de Cloudflare. Ce dernier fonctionne au niveau de la couche 7 du modèle OSI, ce qui sécurise les applications sans modifier l’infrastructure existante ni compromettre les performances.
Ce service utilise des ensembles de règles prédéfinies et personnalisables pour identifier et bloquer les requêtes malveillantes avant qu’elles n’atteignent le serveur d’origine, c’est-à-dire là où vous hébergez vos services.
Autre avantage, vous pouvez visualiser en quasi-temps réel les menaces bloquées pour chaque règle définie.
Attention, la version gratuite de Cloudflare limite à cinq le nombre de règles, il faut donc en mutualiser plusieurs si vous ne voulez pas atteindre trop rapidement ce seuil.
Mutual TLS (mTLS)#
Pour en avoir déjà beaucoup parlé au sein de ce blog notamment du côté de Traefik, le mTLS est une méthode de sécurité qui assure une authentification mutuelle entre deux parties, généralement un client et un serveur. Contrairement au TLS classique, où seul le serveur est authentifié, le mTLS exige que les deux parties présentent et vérifient leurs certificats, garantissant ainsi que chaque partie est bien celle qu’elle prétend être.
Ce concept est très pratique si vous souhaitez exposer des services privés contenant des données sensibles.
Devinez quoi ? Il est possible de générer ces propres certificats au sein de Cloudflare qui seront ensuite signés par l’autorité de certification (CA) du service qui, avec une configuration dédiée, permet de protéger vos sites en demandant un certificat valide au client.
En l’occurrence, ce sera Cloudflare qui vérifiera la validité du certificat client pour donner ensuite accès aux serveurs sous-jacents.
Routage des e-mails#
Autre fonctionnalité un peu plus anecdotique cette fois, bien que parfois forte utile pour masquer son e-mail principal de contact, il est possible de configurer des routes de transfert d’e-mail en créant des adresses avec le nom de domaine associé à Cloudflare.
Des traitements peuvent être réalisés sur les emails avec les workers (un peu comme une Lambda sur AWS) propres à Cloudflare, je vous laisse consulter la documentation si vous désirez en savoir plus.
Pages#
Vous faites du contenu statique avec des CMS comme Gatsby ou Hugo ? Alors Cloudflare Pages est la solution pour les héberger facilement !
Vous bénéficierez de la haute disponibilité du réseau Cloudflare et du déploiement du contenu très rapide via son CDN.
Autre point important : Cloudflare Pages peut se configurer avec un dépôt de code associé à GitLab ou GitHub pour une approche as code très efficace. Il est également possible d’avoir des environnements différents en fonction des branches Git, ce qui permet de tester et valider avant de déployer l’ensemble sur votre site principal.
L’offre gratuite limite le nombre de builds par mois à 500, ce qui est globalement très large pour une utilisation personnelle.
Le code, rien que le code !#
Premières étapes#
Avant de commencer quoi que ce soit, il vous faudra récupérer le code source à travers le dépôt de code GitHub ci-dessous :
Deploy your Cloudflare configuration with OpenTofu!
Deuxième étape, vous devrez créer un token d’API pour interagir avec le provider Cloudflare, mais aussi celui de Mastercard à savoir restapi.
Pour cela, vous devrez aller dans Profile
> API Tokens
> Create Token
et ajouter les permissions suivantes :
- Compte - Adresses de routage des e-mails - Modifier
- Compte - Cloudflare Pages - Modifier
- Compte - Access: Certificats TLS mutuels - Modifier
- Compte - Scripts de Workers - Modifier
- Compte - Paramètres de compte - Lu
- Compte - Access: Apps et politiques - Modifier
- Zone - Règles de routage des e-mails - Modifier
- Zone - Zone WAF - Modifier
- Zone - Access: Apps et politiques - Modifier
- Zone - Réglages Zone - Modifier
- Zone - Zone - Lu
- Zone - SSL et Certificats - Modifier
- Zone - DNS - Modifier
Par souci de simplicité, j’ai choisi d’utiliser un backend de type local
, à vous de voir si vous souhaitez modifier cela. Pour un projet personnel, cela peut avoir du sens, surtout que ce dernier est entièrement chiffré.
En parlant de chiffrement, et c’est une grande nouveauté d’OpenTofu, vous trouverez la configuration au sein du fichier infra/encryption.tf
:
terraform {
encryption {
key_provider "pbkdf2" "encryption_key" {
passphrase = var.encryption_passphrase
}
method "aes_gcm" "encryption_method" {
keys = key_provider.pbkdf2.encryption_key
}
state {
method = method.aes_gcm.encryption_method
enforced = true
}
plan {
method = method.aes_gcm.encryption_method
enforced = true
}
}
}
Pour faire simple, le provider PBKDF2 se base sur une passphrase que j’ai variabilisée grâce à une fonctionnalité de la version 1.8. Les blocs state
et plan
utilisent la méthode de chiffrement définie juste au-dessus.
Dernière étape, il sera nécessaire de renseigner plusieurs variables d’environnements :
export CLOUDFLARE_API_TOKEN= # Le token d'API de Cloudflare (provider officiel)
export TF_VAR_cloudflare_api_token=$CLOUDFLARE_API_TOKEN # Le token d'API de Cloudflare pour le provider restapi
export TF_VAR_encryption_passphrase= # La passphrase de chiffrement pour le state et le plan
Vous pouvez mettre ces dernières dans un fichier .envrc
au sein du dépôt de code si vous utilisez direnv.
La configuration#
L’ensemble du code se trouve dans le dossier infra
et les fichiers de configuration sont contenus dans le dossier infra/configurations
. Vous pouvez prendre exemple sur le fichier example-org.tfvars
, le dupliquer et utiliser votre nom de domaine comme nom du fichier pour plus de lisibilité.
Le fichier infra/outputs.tf
, vous donnera la configuration DNSSEC associée à votre nom de domaine et les adresses IP des serveurs Cloudflare si vous souhaitez autoriser uniquement celles-ci depuis vos services.
Zone#
Première étape, la configuration du nom de votre zone :
zone_name = "example.org"
Cela récupérera l’identifiant de cette dernière et rendra possible la création de différentes ressources liées à celle-ci.
Paramétrage#
Le fichier infra/settings.tf
contient la configuration de votre zone, configurable à travers la variable zone_settings
:
zone_settings = {
always_online = "on"
always_use_https = "on"
automatic_https_rewrites = "on"
brotli = "on"
early_hints = "on" # Improve page load speeds
email_obfuscation = "on"
fonts = "on"
http3 = "off" # Disabled to avoid mTLS issues
ip_geolocation = "on"
min_tls_version = "1.3"
ssl = "full"
rocket_loader = "on"
tls_1_3 = "zrt"
websockets = "on"
zero_rtt = "on"
}
Libre à vous de modifier ces valeurs. Attention à la partie http3
qui doit être désactivée pour que le mTLS fonctionne correctement. En effet, c’est une limitation connue.
DNS#
Pour la partie DNS, la variable dns_records
offre la possibilité d’ajouter vos enregistrements sous forme d’objets. À noter que le paramètre proxied
doit être activé si vous souhaitez configurer des règles WAF pour cet enregistrement, comme dit plus haut.
dns_records = [
##
# A records
##
{ name = "server", value = "x.x.x.x", type = "A", proxied = true },
##
# CNAME records
##
{ name = "website", value = "server.example.org", type = "CNAME", proxied = true },
{ name = "prometheus", value = "server.example.org", type = "CNAME", proxied = true },
]
mTLS#
On continue avec le mTLS, totalement configurable, lui aussi.
Pour en bénéficier et générer vos propres certificats, plusieurs valeurs comme l’organisation, la ville, le pays, etc. sont à configurer. Ces valeurs seront injectées dans la Certificate Signing Request (CSR) avant de demander la signature du certificat par le CA de Cloudflare.
mtls_certificate_configuration = {
common_name = "example.org"
organization = "My organization"
locality = "My city"
postal_code = "00000"
province = ""
country = "US"
}
De plus, c’est généralement une bonne pratique d’avoir un certificat nominatif pour chaque utilisateur, de manière à le révoquer si besoin. C’est pourquoi la variable mtls_certificate_users
vous permet de générer un certificat par entrée.
mtls_certificate_users = [
"axinorm",
]
Ensuite, il est possible de configurer les noms de domaine ou sous-domaines qui sont soumis au mTLS avec la variable mtls_hostnames
:
mtls_hostnames = [
{ domain_name = "server.example.org", create_mtls_waf_rule = true },
]
L’option create_mtls_waf_rule
donne la possibilité de définir une règle côté WAF qui vérifie la validité du certificat et que celui-ci n’est pas révoqué avant d’accorder l’accès aux services cibles.
De plus, mtls_client_certificate_forwarding
active un paramètre qui transmet deux headers (Cf-Client-Cert-Der-Base64
et Cf-Client-Cert-Sha256
) aux serveurs protégés par Cloudflare dans le cas où vous souhaitez vérifier de nouveau la présence ou validité du ou des certificats au sein de vos services.
Une fois les certificats générés, ils sont récupérables dans le dossier mtls-certificates
. Vous pourrez ainsi configurer vos navigateurs web ou applications.
WAF#
Pour la partie WAF, j’ai configuré plusieurs types de règles :
- La première pour le mTLS, se construit automatiquement avec la variable
mtls_hostnames
et l’optioncreate_mtls_waf_rule
àtrue
.
mtls_rule_waf_expression = <<EOT
not (
cf.tls_client_auth.cert_verified and
not cf.tls_client_auth.cert_revoked and
cf.tls_client_auth.cert_subject_dn contains "O=${var.mtls_certificate_configuration.organization}" and
cf.tls_client_auth.cert_subject_dn contains "L=${var.mtls_certificate_configuration.locality}" and
cf.tls_client_auth.cert_subject_dn contains "ST=${var.mtls_certificate_configuration.province}" and
cf.tls_client_auth.cert_subject_dn contains "C=${var.mtls_certificate_configuration.country}"
) and (
http.host in {"${join("\" \"", local.mtls_waf_rule_hostnames)}"}
)
EOT
En langage Cloudflare, pour la liste de noms de domaines ou sous-domaines protégés, le certificat doit être valide et il ne doit pas être révoqué. De plus, différentes informations sont vérifiées pour confirmer les données injectées au sein du certificat.
- La deuxième règle configurable avec la variable
waf_ip_whitelist_rule
autorise uniquement certaines IPs définies à accéder à une liste de domaines.
geolocation_rule_waf_expression = <<EOT
(
not ip.geoip.country in {"${join("\" \"", var.waf_geolocation_whitelist_rule.restricted_countries)}"}
) and (
http.host in {"${join("\" \"", var.waf_geolocation_whitelist_rule.domain_names)}"}
)
EOT
- Troisième et dernière règle prédéfinie, la possibilité de limiter par pays certains domaines avec la variable
waf_geolocation_whitelist_rule
. Cela limite une trop grande exposition de vos services à une liste de pays configurable.
ip_whitelist_rule_waf_expression = <<EOT
(
not ip.src in {${join(" ", var.waf_ip_whitelist_rule.restricted_ips)}}
) and (
http.host in {"${join("\" \"", var.waf_ip_whitelist_rule.domain_names)}"}
)
EOT
- On termine avec la variable
waf_custom_rules
. Ici, vous pouvez ajouter vos propres règles personnalisées en fonction de vos besoins.
Routage des e-mails#
Pour vos transferts d’e-mails, la variable email_routing_rules
est configurable. À noter que les adresses e-mails cibles doivent être vérifiées pour confirmer la possibilité de rediriger du courrier vers celles-ci.
email_routing_rules = [
{
name = "pro"
enabled = true
matcher = {
type = "literal"
field = "to"
value = "[email protected]"
}
action = {
type = "forward"
value = [
"[email protected]",
]
}
},
]
Si au moins une entrée est présente dans cette variable, la ressource cloudflare_email_routing_settings
configurera automatiquement les enregistrements DNS demandés.
Pages#
Dernière partie et pas la moindre, la partie Cloudflare Pages qui donne la possibilité de mettre du contenu statique en ligne :
pages = [
{
name = "blog"
production_branch = "main"
custom_domains = [
{
domain = "example.org"
branch = "main"
},
{
domain = "dev.example.org"
branch = "dev"
},
]
source = {
type = "github"
config = {
owner = "my_user"
repo_name = "blog"
production_branch = "main"
pr_comments_enabled = true
deployments_enabled = true
production_deployment_enabled = true
preview_deployment_setting = "custom"
preview_branch_includes = ["dev"]
preview_branch_excludes = ["main"]
}
}
}
]
Au sein de cet exemple, plusieurs choses :
production_branch
permet de définir la branche principale de votre site ;- La liste de
custom_domains
permet de lier des noms de domaine personnalisés à Cloudflare Pages, ce qui évite d’avoir une URL en .pages.dev. Il est possible d’avoir plusieurs domaines par branche mais aussi de définir des domaines pour des branches autres que la branche principale. Par exemple, si une branche de votre dépôt Git s’appelledev
, alors dev.example.org redirigera vers le contenu de la branche ;- Chaque entrée de la liste
custom_domains
fera l’objet d’un ajout côté enregistrements DNS et d’une validation de celui-ci pour que Cloudflare puisse rediriger le trafic depuis celui-ci. Ces manipulations sont effectuées avec les ressourcescloudflare_record.pages
etrestapi_object.pages_dns_validation
du fichierinfra/pages.tf
.
- Chaque entrée de la liste
- La configuration de GitHub ou GitLab se fait dans le bloc
config
. Vous devrez donner la permission à Cloudflare d’être en lecture sur votre dépôt de code avant de pouvoir déployer cette configuration.
Si vous avez besoin de construire la partie statique de votre site de manière dynamique, un bloc build_config
est configurable également, je vous laisse consulter le fichier infra/variables.tf
pour les paramètres à définir.
Restapi, plus qu’un provider#
On termine avec le provider restapi
de MasterCard qui est assez incroyable, je dois l’avouer. Celui-ci permet de configurer directement les APIs que vous souhaitez utiliser au sein d’OpenTofu ou Terraform, ce qui fait que vous n’avez pas besoin de provider officiel si vous disposez d’une documentation assez riche sur les APIs des ressources que vous voulez créer.
Exemple ici avec le mTLS au sein du fichier infra/mtls.tf
, le provider officiel ne dispose pas, pour l’instant, de ressource pour créer des certificats clients et les signer avec le CA de Cloudflare. C’est pourquoi, j’ai configuré la ressource restapi_object
en suivant la documentation de l’API :
resource "restapi_object" "certificate" {
for_each = var.mtls_certificate_users
path = "/zones/${data.cloudflare_zone.this.id}/client_certificates"
data = jsonencode({
csr = tls_cert_request.this[each.key].cert_request_pem
validity_days = 3650
})
id_attribute = "result/id"
force_new = [
sha256(jsonencode({
csr = tls_cert_request.this[each.key].cert_request_pem
}))
]
}
Si la configuration de la ressource peut paraître fastidieuse, le cycle de vie des ressources au sein d’OpenTofu ou Terraform est totalement configurable. En effet, il est possible de définir les comportements de création, de mise à jour et de suppression avec les champs create_method
, create_path
, update_method
, update_path
, etc.
Je vous recommande honnêtement de l’utiliser si jamais vous tombez sur le même besoin que le mien.
Déploiement#
Pour aller plus vite et vous simplifier la vie, j’ai ajouté un petit script tofutil.sh
qui prend deux paramètres :
-a
pour l’action à réaliser : plan, apply ou destroy ;-c
pour le fichier de configuration sans son extension .tfvars, par exemple-c example-org
.
L’avantage de l’utiliser est qu’il formate votre code et le valide avant n’importe quelle action.
Néanmoins, vous pouvez utiliser les commandes tofu
classiques :
# Initialisation du backend
tofu init -backend-config=path= # À configurer avec le chemin du backend
# Valide votre configuration
tofu validate -var-file= # À configurer avec le fichier de configuration
# Plan or Apply
tofu plan -var-file= # À configurer avec le fichier de configuration
tofu apply -var-file= # À configurer avec le fichier de configuration
C’est à vous de jouer maintenant !
Un mot pour conclure#
Rien de mieux qu’une configuration as code pour configurer Cloudflare et en même temps prendre en main OpenTofu !
Bien qu’assez jeune, OpenTofu regorge d’avenir en étant très proche des aspirations de la communauté, il est donc un candidat idéal côté outils d’infrastructure as code, mais surtout pour remplacer petit à petit Terraform.
Enfin, cet article résulte d’un défi que je m’étais lancé juste avant l’été, j’espère que vous pouvez profiter de ces quelques lignes de code si comme moi, vous avez un nom de domaine et quelques services à protéger avant de les exposer sur internet. :-)