# Déployer un site Hugo sur AWS avec Terraform, CloudFront OAC et GitHub Actions

> Déployer un site statique Hugo sur AWS avec Terraform : CloudFront et son Origin Access Control, ACM, GitHub Actions et des credentials OIDC.

2023-10-31 · AWS, Hugo, Terraform, CI-CD

Dans mon [article précédent](https://mehdilaruelle.com/fr/posts/2022/08/deployer-votre-site-hugo-sur-aws-avec-terraform/), nous avons vu comment mettre en place
un site web statique avec [Hugo](https://gohugo.io/) sur [AWS](https://aws.amazon.com/) via [Terraform](https://www.terraform.io/)
notamment celui qui héberge ce site.

Cela fait maintenant un peu plus d'un an (voire deux) que ce site est déployé de cette façon et j'ai eu l'occasion
d'améliorer/changer plusieurs choses.

Dans cet article nous verrons les nouveautés apportées sur ce site notamment:
- L'usage de [OAC](https://aws.amazon.com/fr/blogs/networking-and-content-delivery/amazon-cloudfront-introduces-origin-access-control-oac/) à la place de l'[OAI](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html)
- L'upgrade du provider [Terraform AWS en v5](https://registry.terraform.io/providers/BigEyeLabs/aws-test/latest/docs/guides/version-5-upgrade)
- L'ajout de [GitHub Action](https://docs.github.com/en/actions) (pour déployer le site automatiquement) et de l'injection des credentials AWS de façon dynamique
- L'amélioration de la stack Terraform avec l'autoconfiguration d'[ACM (AWS Certificate Manager)](https://aws.amazon.com/fr/certificate-manager/) avec [AWS Route53](https://aws.amazon.com/fr/route53/)

**TL;TR**: Pour ceux qui souhaitent déployer leurs infrastructures sur AWS avec la nouvelle version, voici le [repository GitHub](https://github.com/mehdilaruelle/terraform-aws-hugo-website)

## Prérequis

Pour comprendre ce post, vous devez:
- Avoir des notions sur [AWS](https://aws.amazon.com/)
- Connaître Terraform et notamment [déployer sur AWS via Terraform](https://learn.hashicorp.com/collections/terraform/aws-get-started)
- Avoir vu l'[article précédent](https://mehdilaruelle.com/fr/posts/2022/08/deployer-votre-site-hugo-sur-aws-avec-terraform/) pour comprendre le contexte

Pour reproduire l'exemple de ce blog, vous devez :
- Avoir un [compte AWS et des credentials](https://learn.hashicorp.com/tutorials/terraform/aws-build?in=terraform/aws-get-started#prerequisites)
- Avoir un nom de domaine
- Déployer l'infrastructure sur AWS via [Terraform](https://learn.hashicorp.com/collections/terraform/aws-get-started)

## Rappel de l'infrastructure

Dans l'[article précédent](https://mehdilaruelle.com/fr/posts/2022/08/deployer-votre-site-hugo-sur-aws-avec-terraform/) nous avons déployé l'infrastructure suivante:
<img src="/hugo-website/hugo_aws_website_hu_a5903c063b048d1d.webp" loading="lazy" decoding="async" srcset="/hugo-website/hugo_aws_website_hu_86e155cbbe840961.webp 480w, /hugo-website/hugo_aws_website_hu_a5903c063b048d1d.webp 732w" sizes="(max-width: 800px) 100vw, 800px" width="732" height="441" alt="Architecture d&#39;un site statique sur AWS S3 derrière CloudFront" class="left" />

Nous avons:
- **Amazon Route 53**: Qui héberge notre Hosted Zone et notre domaine (`mehdilaruelle.com`)
- **Amazon CloudFront**: Qui fait office de CDN (Content Delivery Network) et point d'entrée pour nos end users
- **AWS Certificate Manager**: Qui va héberger notre certificat et être utilisé par Amazon CloudFront pour le `HTTPS`
- **Un bucket S3**: Qui hébergera notre site web statique déployé par Hugo et uniquement accessible par Amazon CloudFront. Notre bucket est donc `privé`

L'ensemble de [l'infrastructure est déployé par Terraform](https://github.com/mehdilaruelle/terraform-aws-hugo-website).

## Remplacement de l'OAI par l'OAC

Dans l'architecture que nous avons vue, le bucket S3 est privé et est uniquement accessible par Amazon CloudFront. 
Anciennement, la méthode utilisée était l'[Origin Access Identity (OAI)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#private-content-restricting-access-to-s3-oai) qui est aujourd'hui dépréciée.
En guise de remplacement, AWS a mis en place l'[Origin Access Control (OAC)](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#create-oac-overview-s3).

Contrairement à l'OAI qui était vu comme un principal IAM CloudFront, ici l'OAC est vu comme une ressource AWS CloudFront.
Les avantages de l'OAC par rapport à l'OAI sont le support de:
- Tous les buckets S3 dans toutes les régions.
- Chiffrement server-side encryption avec AWS KMS (SSE-KMS)
- Requêtes dynamiques (PUT et DELETE) vers Amazon S3

Cette migration est plutôt simple car il suffit de:
1. [Créer un OAC](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudfront_origin_access_control)
2. [Mettre la bucket policy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#migrate-from-oai-to-oac)
3. Mettre à jour Amazon CloudFront en remplaçant l'OAI par l'OAC

Vous retrouverez [le commit GitHub concernant cette migration](https://github.com/mehdilaruelle/terraform-aws-hugo-website/commit/163ce5812f9c338da4c9fcd8eb8f5ebc5e24e8dd) ou encore
la [documentation officielle d'AWS sur le sujet](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#migrate-from-oai-to-oac).

## Upgrade du Terraform provider AWS v5

Ce n'est pas tous les jours que le provider `AWS` de Terraform reçoit une mise à jour majeure et c'est donc tout naturellement que l'upgrade s'imposait.
Pour les plus curieux qui souhaitent en savoir plus sur les nouveautés du provider, je vous invite à [regarder le Upgrade Guide d'HashiCorp](https://registry.terraform.io/providers/BigEyeLabs/aws-test/latest/docs/guides/version-5-upgrade#provider-version-configuration)

Aucun impact au niveau du code Terraform mais je préfère explicitement l'indiquer au niveau du code Terraform:
```hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~>5.0"
    }
  }
}
```

Pour ceux qui ne connaissent pas, vous avez [tfupdate](https://github.com/minamijoyo/tfupdate)
utilisable via le [pre-commit-terraform](https://github.com/antonbabenko/pre-commit-terraform#tfupdate) pour rester à jour sur vos dépendances.

## Github action et credentials dynamiques AWS

Jusqu'à peu, les déploiements de mes blog posts ou mise à jour des thèmes se faisaient localement depuis mon terminal car ma fréquence de déploiement de mes blog posts
était assez faible. Cependant, je faisais face à 2 problématiques:
1. À chaque déploiement, je pouvais avoir des problèmes de résidus de cache ou encore d'autres anomalies liées à mon environnement
2. Des credentials AWS statiques qui étaient utilisés une fois tous les 3 mois au mieux. Plutôt dommage pour des credentials statiques

Pour répondre à ces problématiques, j'ai:
1. Mis en place GitHub Action (gratuit jusqu'à 2000 minutes par mois) pour automatiser le déploiement de mon site à chaque `git push`
2. Mise en place de credentials AWS temporaires à chaque déploiement dans GitHub action

### Mise en place de GitHub Action

Mon objectif est: "À chaque `push` sur ma branche `master`, je veux que mon site `Hugo` soit déployé sur `S3`".
Bonne nouvelle, sur [GitHub Action](https://docs.github.com/en/actions) c'est possible de le faire (et gratuitement) assez facilement car il existe des [Actions](https://docs.github.com/en/actions/learn-github-actions/finding-and-customizing-actions).

Dans un premier temps, j'ai utilisé l'[Action Hugo](https://github.com/peaceiris/actions-hugo) de [peaceiris](https://github.com/peaceiris)
pour l'usage de Hugo dans Github Action.

Voilà le contenu de mon fichier `.github/workflows/main.yml` de GitHub action:
```yaml
name: Hugo deploy website on S3

on:
  push:
    branches: [ main ] # Only when a push is done on main branch

permissions:
      contents: read    # This is required for actions/checkout

jobs:
  Build_and_Deploy:
    runs-on: ubuntu-latest
    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
    - name: Git clone the repository
      uses: actions/checkout@v4
      with:
        submodules: 'true' # Some Hugo theme are imported as a submodule, so you need it
    # Sets up Hugo with latest version
    - name: Setup Hugo
      uses: peaceiris/actions-hugo@v2
      with:
          hugo-version: 'latest'
          extended: true
    - name: Build
      run: hugo --minify
    - name: Deploy to S3
      run: hugo deploy --force
```

Si nous résumons les étapes de notre job, nous avons:
1. **Checkout**: Permet de cloner notre repository avec les submodules nécessaires pour les Hugo thèmes
2. **Setup Hugo**: Installation de **Hugo** en édition [extended](https://gohugo.io/installation/linux/#editions).
  car certains de mes thèmes utilisent des fonctionnalités de cette édition
3. **Build**: On construit notre site web statique en version [minify](https://en.wikipedia.org/wiki/Minification_(programming))
4. **Deploy**: Enfin, on déploie notre site web Hugo sur S3

Pour que Hugo sache comment déployer, au niveau de l'étape 4, il faudra ajouter la bonne configuration au niveau du `config.toml`.
Dans mon cas, le bout de configuration ressemble à ça:
```toml
  [deployment]

  [[deployment.targets]]
    name = "aws"
    URL = "s3://hugo-website-mlaruelle?region=eu-west-3"
```

<div class="notice note" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 128 300 300">
  <path d="M150 128c82.813 0 150 67.188 150 150 0 82.813-67.188 150-150 150C67.187 428 0 360.812 0 278c0-82.813 67.188-150 150-150Zm25 243.555v-37.11c0-3.515-2.734-6.445-6.055-6.445h-37.5c-3.515 0-6.445 2.93-6.445 6.445v37.11c0 3.515 2.93 6.445 6.445 6.445h37.5c3.32 0 6.055-2.93 6.055-6.445Zm-.39-67.188 3.515-121.289c0-1.367-.586-2.734-1.953-3.516-1.172-.976-2.93-1.562-4.688-1.562h-42.968c-1.758 0-3.516.586-4.688 1.563-1.367.78-1.953 2.148-1.953 3.515l3.32 121.29c0 2.734 2.93 4.882 6.64 4.882h36.134c3.515 0 6.445-2.148 6.64-4.883Z"/>
</svg>

        </span> Remarque </p><p>L&rsquo;URL doit contenir la region. La région dans mon cas est <code>eu-west-3</code> (Paris).</p></div>
 

À ce stade, **presque** tout est fonctionnel. En effet, il nous manque les droits pour déployer sur le bucket S3.

### Mise en place des credentials AWS temporaires

Jusqu'à présent, j'utilisais un [IAM user](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html) avec des credentials statiques.
Sachant que maintenant les déploiements se font via GitHub action, je me suis dit qu'il était temps de passer à des credentials temporaires
afin d'avoir **"des credentials à usage unique à chaque déploiement"**.

Pour ce faire, j'ai utilisé un [AWS Assume Role With Web Identity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html) avec [GitHub Action](https://github.com/aws-actions/configure-aws-credentials#configure-aws-credentials-for-github-actions).

Je vous invite à en apprendre plus sur l'ensemble du process et de la configuration [via la documentation officielle de GitHub Action](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services).
Dans notre cas, nous allons résumer la migration vers cette méthode en 2 étapes:
1. La mise en place de la Web Identity
2. La mise en place du GitHub Action `configure-aws-credentials-for-github-actions`

#### Étape 1: Mise en place de la Web Identity

Cette première étape consiste à mettre en place un `Identity Provider` (de type [OIDC](https://auth0.com/intro-to-iam/what-is-openid-connect-oidc)) au niveau du service IAM
et établir un lien de **trust** de IAM Identity Provider vers GitHub Action sur lequel GitHub Action pourra assumer notre rôle.
En version simplifiée, cela représente ça:
<img src="/hugo-website/step1_webidentity_hu_f4d7a7c3c9f1b083.webp" loading="lazy" decoding="async" srcset="/hugo-website/step1_webidentity_hu_e1b68a075542ed75.webp 480w, /hugo-website/step1_webidentity_hu_b181beced6515735.webp 800w, /hugo-website/step1_webidentity_hu_f4d7a7c3c9f1b083.webp 921w" sizes="(max-width: 800px) 100vw, 800px" width="921" height="327" alt="Fournisseur d&#39;identité IAM avec GitHub" class="left" />

Pour ce faire, j'ai mis en place [un Terraform pour mettre en place la Web Identity que j'invite les plus curieux à regarder](https://github.com/mehdilaruelle/terraform-aws-hugo-website/blob/master/github-action.tf).

Dans le [repository Terraform](https://github.com/mehdilaruelle/terraform-aws-hugo-website/tree/master), pour activer le déploiement de la Web Identity pour votre repository GitHub, 
vous devez fournir les valeurs aux variables `github_org` (le nom de l'organisation GitHub) et `github_repositories` (Une liste de repositories GitHub autorisés à assumer le Web Identity role).
Dans mon cas, cela donne ça:
```hcl
github_org          = "mehdilaruelle" # GitHub Organization name
github_repositories = ["blog_hugo"] # The list of GitHub repositories to allow to assume the Web Identity role
```

Puis faites un `terraform apply` et récupérez l'output de `aws_role_arn` via un `terraform output aws_role_arn`.
Gardez l'output, nous en aurons besoin pour la configuration de notre GitHub Action.

#### Étape 2: Mise en place du GitHub Action configure-aws-credentials-for-github-actions

Enfin, l'étape 2 consiste à ce que GitHub Action puisse assumer notre rôle.
Toujours en version simplifiée, cela représente ça:
<img src="/hugo-website/step2_workflow_hu_a0150da76c7ab632.webp" loading="lazy" decoding="async" srcset="/hugo-website/step2_workflow_hu_305384d8720a7275.webp 480w, /hugo-website/step2_workflow_hu_875e75e7e6bb9ca4.webp 800w, /hugo-website/step2_workflow_hu_a0150da76c7ab632.webp 926w" sizes="(max-width: 800px) 100vw, 800px" width="926" height="379" alt="GitHub Actions assumant un rôle via web identity" class="left" />

Nous allons compléter notre GitHub Action Workflow avec l'action [configure-aws-credentials-for-github-actions](https://github.com/aws-actions/configure-aws-credentials#configure-aws-credentials-for-github-actions).
Pour ce faire, il faudra:
1. Ajouter la **permission** à notre workflow d'utiliser un [JSON Web Token](https://jwt.io/) GitHub.
2. Ajouter la **step** avec notre GitHub Action [configure-aws-credentials-for-github-actions](https://github.com/aws-actions/configure-aws-credentials#configure-aws-credentials-for-github-actions) tout en mettant notre `aws_role_arn` en paramètre de la step.

Ce qui nous donne le rendu final et fonctionnel suivant:
```yaml
name: Hugo deploy website on S3

on:
  push:
    branches: [ main ]

permissions:
      id-token: write   # This is required for requesting the JWT
      contents: read    # This is required for actions/checkout

jobs:
  Build_and_Deploy:
    runs-on: ubuntu-latest
    steps:
    # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
    - name: Git clone the repository
      uses: actions/checkout@v4
      with:
        submodules: 'true'
    - name: configure aws credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        role-to-assume: arn:aws:iam::012345678912:role/GitHubOIDCRole # To replace with aws_role_arn
        role-session-name: githubActionHugoDeploy
        aws-region: eu-west-3
    # Sets up Hugo with latest version
    - name: Setup Hugo
      uses: peaceiris/actions-hugo@v2
      with:
          hugo-version: 'latest'
          extended: true
    - name: Build
      run: hugo --minify
    - name: Deploy to S3
      run: hugo deploy --force
```

Avec le résultat suivant, à la suite d'un `git push`, via GitHub Action:
<img src="/hugo-website/github_action.png" loading="lazy" decoding="async" width="292" height="435" alt="Exécution GitHub Actions" class="left" />

## Auto setup d'ACM (AWS Certificate Manager)

Dans la construction du site web, nous partons du principe dans l'[article précédent](https://mehdilaruelle.com/fr/posts/2022/08/deployer-votre-site-hugo-sur-aws-avec-terraform/)
que le nom de domaine (ou sous domaine) est hébergé sur Amazon Route 53 et que la [Hosted Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/AboutHZWorkingWith.html) doit être créée manuellement.

Jusqu'à récemment, la création du certificat dans AWS Certificate Manger (ACM) devait aussi être manuelle. Chose révolue
à la suite de [ce commit](https://github.com/mehdilaruelle/terraform-aws-hugo-website/commit/620f4256444457dac919c93535a9a83d91996e05)
qui peut se résumer au travers du snippet suivant:
```hcl
resource "aws_acm_certificate" "hugo" {
  provider          = aws.aws_cloudfront # CloudFront uses certificates from US-EAST-1 region only
  domain_name       = local.dns_name
  validation_method = "DNS"
}

resource "aws_route53_record" "hugo" {
  for_each = {
    for dvo in aws_acm_certificate.hugo.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  allow_overwrite = true
  name            = each.value.name
  records         = [each.value.record]
  ttl             = 60
  type            = each.value.type
  zone_id         = data.aws_route53_zone.hugo.zone_id
}

resource "aws_acm_certificate_validation" "hugo" {
  provider                = aws.aws_cloudfront # CloudFront uses certificates from US-EAST-1 region only
  certificate_arn         = aws_acm_certificate.hugo.arn
  validation_record_fqdns = [for record in aws_route53_record.hugo : record.fqdn]
}
```

<div class="notice info" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="92 59.5 300 300">
  <path d="M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z"/>
</svg>

        </span> Information </p><p>Vous pouvez retrouver ce snippet directement dans <a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation">la documentation officielle d&rsquo;HashiCorp</a>.</p></div>
 

## Nouveau domain name

Enfin, dernière nouveauté concernant le site: le changement de nom de domaine de `blog.mehdilaruelle.ninja` en `mehdilaruelle.com`
lié notamment à l'augmentation du prix des noms de domaine global et surtout celui du `.ninja`.

Ce changement m'a posé plusieurs défis, notamment:
- Le changement de configuration de Hugo dans le `config.toml` (plutôt facile)
- Le certificat (plutôt facile avec l'automatisation via Terraform)
- La redirection de `blog.mehdilaruelle.ninja` vers `mehdilaruelle.com` sachant que l'hébergement du nom de domaine est situé côté [OVH](https://www.ovhcloud.com/fr/domains/)

Le dernier défi m'a notamment poussé à mettre en place un mécanisme de redirection avec préservation du path (chose inexistante gratuitement côté OVH) en serverless via [Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) mais cela fera l'objet d'un autre article.

## Conclusion

Les différentes améliorations du site web m'ont apporté une expérience de release beaucoup plus fluide et transparente, notamment via
l'intégration de **GitHub Action** et des **credentials dynamiques AWS**.
D'autre part, pour les mises à jour Terraform, vous pouvez passer par [tfupdate](https://github.com/minamijoyo/tfupdate)
au travers du [pre-commit-terraform](https://github.com/antonbabenko/pre-commit-terraform#tfupdate) pour rester le plus à jour sur vos dépendances.

<div class="notice info" >
    <p class="notice-title">
        <span class="icon-notice baseline">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="92 59.5 300 300">
  <path d="M292 303.25V272c0-3.516-2.734-6.25-6.25-6.25H267v-100c0-3.516-2.734-6.25-6.25-6.25h-62.5c-3.516 0-6.25 2.734-6.25 6.25V197c0 3.516 2.734 6.25 6.25 6.25H217v62.5h-18.75c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h87.5c3.516 0 6.25-2.734 6.25-6.25Zm-25-175V97c0-3.516-2.734-6.25-6.25-6.25h-37.5c-3.516 0-6.25 2.734-6.25 6.25v31.25c0 3.516 2.734 6.25 6.25 6.25h37.5c3.516 0 6.25-2.734 6.25-6.25Zm125 81.25c0 82.813-67.188 150-150 150-82.813 0-150-67.188-150-150 0-82.813 67.188-150 150-150 82.813 0 150 67.188 150 150Z"/>
</svg>

        </span> Information </p><p>Dans le repository Terraform pour déployer ce site, <a href="https://github.com/mehdilaruelle/terraform-aws-hugo-website/blob/master/.pre-commit-config.yaml">le pre-commit-terraform est utilisé</a>.</p></div>
 

Enfin, dans un prochain blog post, nous aurons l'occasion de parler plus en détail du mécanisme de redirection avec préservation du path en serverless via [Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html).
Si vous avez d'autres idées d'améliorations, n'hésitez pas à m'écrire.

---

Publié à l’origine sur https://mehdilaruelle.com/fr/posts/2023/10/deployer-votre-site-hugo-sur-aws-avec-terraform-v2/
