<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>The Ops Community ⚙️: Xavier Mignot</title>
    <description>The latest articles on The Ops Community ⚙️ by Xavier Mignot (@xaviermignot).</description>
    <link>https://community.ops.io/xaviermignot</link>
    <image>
      <url>https://community.ops.io/images/-pHtJRyRi92VZksBH36AXOjXaWyaaVgLR-zeuob6vl4/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL3Vz/ZXIvcHJvZmlsZV9p/bWFnZS85Mi8yMTgx/MWQ1Mi02NTZhLTQ2/OWItODc5YS1kNTUy/ZDFhZWZmOGUuanBl/Zw</url>
      <title>The Ops Community ⚙️: Xavier Mignot</title>
      <link>https://community.ops.io/xaviermignot</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://community.ops.io/feed/xaviermignot"/>
    <language>en</language>
    <item>
      <title>Azure App Service, deployment slots (with configuration !) and Terraform</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Mon, 26 Dec 2022 22:35:22 +0000</pubDate>
      <link>https://community.ops.io/xaviermignot/azure-app-service-deployment-slots-with-configuration-and-terraform-3lj2</link>
      <guid>https://community.ops.io/xaviermignot/azure-app-service-deployment-slots-with-configuration-and-terraform-3lj2</guid>
      <description>&lt;p&gt;When you want to implement blue-green deployment using Azure App Services, deployment slots is the way to go. You can create a &lt;em&gt;staging&lt;/em&gt; slot to deploy the new version of your code, test it and &lt;em&gt;swap&lt;/em&gt; with the &lt;em&gt;production&lt;/em&gt; slot once ready to make the new version go live. All of this without causing downtime.&lt;br&gt;
&lt;a href="https://i.giphy.com/media/uKpWZU3VXLprW/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/uKpWZU3VXLprW/giphy.gif" alt="Indiana Jones swap" width="480" height="204"&gt;&lt;/a&gt;&lt;br&gt;
Another main feature of App Service is configuration with &lt;em&gt;app settings&lt;/em&gt;, who are environment variables set at the service level and injected to the application code.&lt;br&gt;&lt;br&gt;
I have been using all of these for several projects over the years, but once we have started using IaC (especially Terraform), things became complicated as swap operations were messing up with the Terraform state.&lt;br&gt;&lt;br&gt;
After almost stopping using slots over the last few years, I have finally found an approach to make them work using Terraform and I'm happy to share it in this post.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By writing this post I assume you already have a good understanding of Terraform and App Service, at least you have tried to use them together 😉&lt;br&gt;&lt;br&gt;
If this sounds new to you I recommend reading my first post about Terraform: &lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://dev.to/xaviermignot/use-terraform-cloud-for-your-pet-projects-3dom" rel="noopener noreferrer"&gt;
      dev.to
    &lt;/a&gt;
&lt;/div&gt;
&lt;br&gt;
as well as some content on App Service (see the &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/overview"&gt;overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/configure-common?tabs=portal#configure-app-settings"&gt;app settings&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/azure/app-service/deploy-best-practices#use-deployment-slots"&gt;deployment slots&lt;/a&gt; pages from the official documentation).
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  The problem with deployment slots and Terraform state
&lt;/h2&gt;

&lt;p&gt;The main issue with deployment slots and Terraform is that the swap operations are usually performed outside of Terraform. After a swap not only the deployed package changes from one slot to another, the swap impacts also all the configuration (including app settings), application stack, Docker image if you are using containers, etc.&lt;br&gt;&lt;br&gt;
All of these properties are included in the Terraform state, so the the next &lt;code&gt;apply&lt;/code&gt; after a swap will try to revert the changes, probably causing a mess.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You might disagree on the &lt;em&gt;"usually performed outside of Terraform"&lt;/em&gt; part of this paragraph. I know there is a resource in the AzureRm provider to perform swaps, I have tried and abandoned it and explain why later in this post.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  TL;DR: The solution, "briefly" explained
&lt;/h2&gt;

&lt;p&gt;If you are here only for the solution and don't want to check the demo and read the whole thing (which is fine), I'll try to explain my approach as briefly as I can in this section.  &lt;/p&gt;

&lt;p&gt;The solution relies on the following principles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;code&gt;active_app&lt;/code&gt; input variable to the main module whose value can be either &lt;code&gt;blue&lt;/code&gt; or &lt;code&gt;green&lt;/code&gt;. The main module also receives two app settings variables: one for the blue version of the app, one for the green one.
These 3 variables are used together by the module creating the App Services resources in the following way: the settings of the active app go to the &lt;em&gt;production&lt;/em&gt; slot, the other to the &lt;em&gt;staging&lt;/em&gt; slot.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;active_app&lt;/code&gt; variable is also set as an &lt;em&gt;output&lt;/em&gt; of the main module. This might sound silly but it's the way I've found to store the following information in the state: &lt;em&gt;What was the active app during the last &lt;code&gt;terraform apply&lt;/code&gt;, the blue one or the green one ?&lt;/em&gt; And this is a key part of the solution.&lt;/li&gt;
&lt;li&gt;The swap operations are made using the Azure CLI with the &lt;code&gt;az webapp deployment slot swap&lt;/code&gt; command.
Right after a swap, the previous &lt;code&gt;active_app&lt;/code&gt; value is retrieved from the state using the &lt;code&gt;terraform output active_app&lt;/code&gt; command. Then a &lt;code&gt;terraform apply&lt;/code&gt; is made using the &lt;em&gt;new&lt;/em&gt; value of the &lt;code&gt;active_app&lt;/code&gt; variable (which is the &lt;em&gt;opposite&lt;/em&gt; of the previous one).
This apply does not changes anything to the infrastructure, it changes the value of the &lt;code&gt;active_app&lt;/code&gt; output, thus saving in the state which version of the app is live.&lt;/li&gt;
&lt;li&gt;Before applying any change to the infrastructure that is not related to a swap, the latest value of the &lt;code&gt;active_app&lt;/code&gt; is retrieved from the state (still using the &lt;code&gt;output&lt;/code&gt; command). The same value is passed as the &lt;code&gt;active_app&lt;/code&gt; &lt;em&gt;variable&lt;/em&gt; so that the properties of the slots are not swapped by the &lt;code&gt;apply&lt;/code&gt; command.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is it, it was not really brief but those 4 points need a minimum amount of details. Still confused ? I got you covered, the rest of this post shows a demo with more in-depth details.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub repository
&lt;/h2&gt;

&lt;p&gt;As I almost always do I have prepared a GitHub repository with a demo consisting of a simple ASP.NET web app, Terraform code and GitHub Actions for deployment: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-app-service-slots"&gt;
        terraform-app-service-slots
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A demo for using Azure App Service deployment slots using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Azure App Service with Terraform, deployment slots and app settings&lt;/h1&gt;
&lt;p&gt;This repository contains a demo on how to use Azure Services deployment slots with Terraform (yes, with app settings too !). It illustrates a blog post published &lt;a href="https://blog.xmi.fr/posts/terraform-app-service-slots/" rel="nofollow"&gt;here&lt;/a&gt;.&lt;br&gt;
The demo consists in :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a few Terraform files to provision an App Service and a deployment slot&lt;/li&gt;
&lt;li&gt;a simple ASP.NET web app&lt;/li&gt;
&lt;li&gt;GitHub Action workflows to
&lt;ul&gt;
&lt;li&gt;provision the Azure resources&lt;/li&gt;
&lt;li&gt;deploy the web app: the &lt;em&gt;blue&lt;/em&gt; version on the &lt;em&gt;production&lt;/em&gt; slot, and the &lt;em&gt;green&lt;/em&gt; version on the &lt;em&gt;staging&lt;/em&gt; slot&lt;/li&gt;
&lt;li&gt;swap the &lt;em&gt;staging&lt;/em&gt; slot with the &lt;em&gt;production&lt;/em&gt; one (as many time of you want)&lt;/li&gt;
&lt;li&gt;destroy the Azure resources once you have finished to save costs&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
Getting started&lt;/h2&gt;
&lt;p&gt;To run this demo by yourself there is nothing to install on you machine as everything is running in the cloud. You need to configure a few things in GitHub and in Terraform Cloud.&lt;/p&gt;…&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-app-service-slots"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;
You can fork this repo and run the demo by yourself by following the instructions in the README file. Or you can stay here, I will explain how things work in the next section.
&lt;h2&gt;
  
  
  The demo, in action
&lt;/h2&gt;

&lt;p&gt;The demo consists in a simple ASP.NET application (running in an App Service of course) presenting a page that can be either blue or green.&lt;br&gt;&lt;br&gt;
The structure of the repository is very classic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;src&lt;/code&gt; folder contains the application code&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;infra&lt;/code&gt;folder contains the Terraform files&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.github/workflows&lt;/code&gt; folder contains the GitHub Actions workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;I took this demo as an opportunity to level-up my GitHub Actions skills but I won't focus on it, as everything can be done locally or from any CI/CD solution.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Provision resources, and deploy code in both slots
&lt;/h3&gt;

&lt;p&gt;Let's start when the &lt;em&gt;blue&lt;/em&gt; version of the application is already running in production, and the &lt;em&gt;green&lt;/em&gt; one being tested in the staging slot.&lt;br&gt;
To fast-forward to this situation, I run the &lt;code&gt;initial-deployment&lt;/code&gt; workflow of the demo to perform the following actions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using Terraform, create a resource group, an App Service Plan, an App Service and a staging slot.&lt;/li&gt;
&lt;li&gt;Checkout the &lt;code&gt;blue&lt;/code&gt; tag of the repo, build the code and deploy the package to the &lt;em&gt;production&lt;/em&gt; slot of the App Service&lt;/li&gt;
&lt;li&gt;Checkout the &lt;code&gt;green&lt;/code&gt; tag of the repo, and also build the code but deploy to the &lt;em&gt;staging&lt;/em&gt; slot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far this is what we have in production:&lt;br&gt;
&lt;a href="https://community.ops.io/images/ewY0GL8v2Vy8iMODajPWNLRuFHg2SHRqnDx1bsbh7dY/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/MS1ibHVlLWFwcC1w/cm9kLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ewY0GL8v2Vy8iMODajPWNLRuFHg2SHRqnDx1bsbh7dY/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/MS1ibHVlLWFwcC1w/cm9kLnBuZw" alt="The blue app in production" width="880" height="300"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;And in staging:&lt;br&gt;
&lt;a href="https://community.ops.io/images/U6PCfSpDJ3-qiPTJ3D987HcB1m_350xGxQxAuSYZ6W0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/Mi1ncmVlbi1hcHAt/c3RhZ2luZy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/U6PCfSpDJ3-qiPTJ3D987HcB1m_350xGxQxAuSYZ6W0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/Mi1ncmVlbi1hcHAt/c3RhZ2luZy5wbmc" alt="The green app in staging" width="880" height="391"&gt;&lt;/a&gt;  You see that red panel over here ? Its presence is managed using deployment slots (or sticky) settings&lt;/p&gt;
&lt;h3&gt;
  
  
  A little glimpse inside the IaC
&lt;/h3&gt;

&lt;p&gt;As you can see in the pics both versions of the app shows a value in &lt;em&gt;italic&lt;/em&gt; that comes from configuration, and a red panel only on the staging slot. This done using the &lt;code&gt;active_app&lt;/code&gt; variable declared like this in the main module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"active_app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt;
  &lt;span class="nx"&gt;validation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;condition&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="err"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"green"&lt;/span&gt; &lt;span class="err"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nx"&gt;error_message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The active_app value must be either 'blue' or 'green' (defaults to 'blue')."&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the call to the &lt;code&gt;app_service&lt;/code&gt; module, this variable is passed alongside the settings for the blue and the green app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"app_service"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt;

  &lt;span class="nx"&gt;blue_app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"EnvironmentLabel"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This is the blue version"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;green_app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;EnvironmentLabel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"This is the green version"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;app_service&lt;/code&gt; module, the &lt;code&gt;active_app&lt;/code&gt; variable is used which settings should be used for the production slot (which is the App Service resource itself):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_linux_web_app"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"app-${var.project}-${var.app_name}"&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="nx"&gt;app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue_app_settings&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;green_app_settings&lt;/span&gt;

  &lt;span class="nx"&gt;sticky_settings&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app_setting_names&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"IsStaging"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;sticky_settings&lt;/code&gt; block ? It is used to display the red panel only on the staging slot, whose creation also relies on the &lt;code&gt;active_app&lt;/code&gt; variable but in a slightly different way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Store the "non-active" app settings in a local&lt;/span&gt;
  &lt;span class="nx"&gt;slot_app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;green_app_settings&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blue_app_settings&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_linux_web_app_slot"&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"staging"&lt;/span&gt;
  &lt;span class="nx"&gt;app_service_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_linux_web_app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;

  &lt;span class="c1"&gt;# Combine the "non-active" app settings with the sticky one&lt;/span&gt;
  &lt;span class="nx"&gt;app_settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;merge&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slot_app_settings&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;IsStaging&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Let's swap !
&lt;/h3&gt;

&lt;p&gt;Swapping is done using the &lt;code&gt;azcli-swap&lt;/code&gt; workflow who first runs the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;az webapp deployment slot swap &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="nv"&gt;$RESOURCE_GROUP_NAME&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nv"&gt;$APP_SERVICE_NAME&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; staging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And just after that it saves the newly active app in the Terraform state using this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;span class="c"&gt;# Get the previous active app from the state...&lt;/span&gt;
&lt;span class="nv"&gt;currentActiveApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; active_app&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# ...and "reverse" it: green turns blue, blue turns green...&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$currentActiveApp&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'green'&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;newActiveApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;newActiveApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;
&lt;span class="c"&gt;# ...finally save the new active app in the state&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt; &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;active_app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$newActiveApp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which produces the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Changes to Outputs:
  ~ active_app &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"blue"&lt;/span&gt; -&amp;gt; &lt;span class="s2"&gt;"green"&lt;/span&gt;

You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.

Apply &lt;span class="nb"&gt;complete&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt; Resources: 0 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back to the browser, the green version is now in production:&lt;br&gt;
&lt;a href="https://community.ops.io/images/jYan-FhF-5-ZuCUbiiDuxp7Z0Sa_mo-r_sHu6osUNSg/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/My1ncmVlbi1hcHAt/cHJvZC5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/jYan-FhF-5-ZuCUbiiDuxp7Z0Sa_mo-r_sHu6osUNSg/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/My1ncmVlbi1hcHAt/cHJvZC5wbmc" alt="The green app in production" width="880" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the blue one has moved to staging:&lt;br&gt;
&lt;a href="https://community.ops.io/images/yS_QgQc_lTtqu_7sTiYUDCs8gAp-PZ3SOYbRNQ-mKKs/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/NC1ibHVlLWFwcC1z/dGFnaW5nLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/yS_QgQc_lTtqu_7sTiYUDCs8gAp-PZ3SOYbRNQ-mKKs/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1hcHAtc2Vy/dmljZS1zbG90cy8w/NC1ibHVlLWFwcC1z/dGFnaW5nLnBuZw" alt="The blue app in staging" width="880" height="391"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  In case of a rollback...
&lt;/h3&gt;

&lt;p&gt;To perform a rollback, just run the &lt;code&gt;azcli-swap&lt;/code&gt; workflow again ! Executing the same steps will put the blue version back in production, and green one in staging, just like before the first swap.&lt;br&gt;&lt;br&gt;
In the real world, this is why it's handy to keep the previous version in the staging slot: even if the new bits have been tested in staging, problems can still occur once in production so better be ready to put things back in place !&lt;/p&gt;
&lt;h3&gt;
  
  
  What about other changes to the infrastructure ?
&lt;/h3&gt;

&lt;p&gt;To apply changes not related to a swap, it's almost as simple as your average &lt;code&gt;terraform apply&lt;/code&gt;. You just need to retrieved the current value of the &lt;code&gt;active_app&lt;/code&gt; output and pass it as a variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;activeApp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; active_app&lt;span class="si"&gt;)&lt;/span&gt;
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt; &lt;span class="nt"&gt;-var&lt;/span&gt; &lt;span class="nv"&gt;active_app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$activeApp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;In the workflow code, the output is done like this: &lt;code&gt;activeApp=$(terraform show -json | jq -r '.values.outputs.active_app.value // "blue"')&lt;/code&gt;&lt;br&gt;&lt;br&gt;
This is to handle the first apply, as the &lt;code&gt;output -raw&lt;/code&gt; command returns an error if the state is empty. I have filled an &lt;a href="https://github.com/hashicorp/terraform/issues/32384"&gt;issue&lt;/a&gt; for this, any 👍 is appreciated 🤗&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A few words about the &lt;code&gt;azurerm_web_app_active_slot&lt;/code&gt; resource
&lt;/h2&gt;

&lt;p&gt;Before closing this post, I want to talk a little about another solution I had tried initially but later abandoned.&lt;br&gt;&lt;br&gt;
The AzureRm Terraform provider provides the &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/web_app_active_slot"&gt;azurerm_web_app_active_slot&lt;/a&gt; resource to perform a swap using Terraform.&lt;br&gt;&lt;br&gt;
There is even a &lt;a href="https://learn.microsoft.com/en-us/azure/developer/terraform/provision-infrastructure-using-azure-deployment-slots"&gt;page&lt;/a&gt; on Microsoft Learn that explains how to use it and has inspired me to write this post.&lt;br&gt;&lt;br&gt;
Initially I had built my demo using this resource like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_web_app_active_slot"&lt;/span&gt; &lt;span class="s2"&gt;"active_slot"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;active_app&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"green"&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="nx"&gt;slot_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_linux_web_app_slot&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;staging&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it worked: applying the configuration with the &lt;code&gt;active_app&lt;/code&gt; set as &lt;code&gt;green&lt;/code&gt; did perform a swap.&lt;br&gt;&lt;br&gt;
Things got weird when I tried to rollback: applying again with &lt;code&gt;active_app&lt;/code&gt; as blue removed the &lt;code&gt;active_slot&lt;/code&gt; resource from the state, but it did not make a swap in the other way. Actually I had to apply the configuration again with &lt;code&gt;active_app&lt;/code&gt; as &lt;em&gt;green&lt;/em&gt; to make the &lt;em&gt;blue&lt;/em&gt; app active again 😖&lt;br&gt;&lt;br&gt;
And it went crazier when I put app settings in the mix: I always ended up with the blue app settings attached to the green app, and vice-versa...&lt;br&gt;&lt;br&gt;
Finally I think that this resource performs an API call to make the swap, aka a one-shot &lt;em&gt;operation&lt;/em&gt; that will make changes on the infrastructure &lt;em&gt;described&lt;/em&gt; else-where in the code-base.&lt;br&gt;&lt;br&gt;
The result is a mix (a mess ?) of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;em&gt;imperative&lt;/em&gt; approach, aka this &lt;code&gt;active_slot&lt;/code&gt; resource who tells &lt;em&gt;"do this swap operation !"&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;a &lt;em&gt;declarative&lt;/em&gt; approach , aka the rest of my Terraform code-base, who tells &lt;em&gt;"hey I need this stuff but I let you do your thing to make it happen"&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And I prefer not to mix both approaches, that's why I ended up separating them and notifying the &lt;em&gt;declarative&lt;/em&gt; stuff (the Terraform state) of the changes made by the &lt;em&gt;imperative&lt;/em&gt; stuff (the swap with az cli).  Damned I really hope this last sentence makes sense 😅&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Getting to the end of this post has been quite a ride, the writing went pretty smooth but once again I have probably spent way too much time on this demo. But I'm happy with the result, I hope this provide &lt;em&gt;one&lt;/em&gt; approach that everyone can use as a starting point to combine the benefits of deployment slots with Terraform.&lt;br&gt;&lt;br&gt;
I have learned a ton doing this, as I had barely touched GitHub Actions before. There was also a few &lt;em&gt;gotchas&lt;/em&gt; in Bash scripting, and I can't remember the last time I built even the simplest webpage without a frontend framework...&lt;br&gt;&lt;br&gt;
Thanks for reading, feel free to reach out to me if you need, and happy coding 🤓&lt;/p&gt;

</description>
      <category>azure</category>
      <category>terraform</category>
      <category>appservice</category>
    </item>
    <item>
      <title>Deploy Azure Logic Apps as code</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Fri, 26 Aug 2022 13:57:15 +0000</pubDate>
      <link>https://community.ops.io/xaviermignot/deploy-azure-logic-apps-as-code-1jl3</link>
      <guid>https://community.ops.io/xaviermignot/deploy-azure-logic-apps-as-code-1jl3</guid>
      <description>&lt;p&gt;I have been playing with Azure Logic Apps on my own lately, and was wondering how these can be managed by teams in an enterprise environment, especially how can we automate the provisioning and deployment of Logic Apps.&lt;br&gt;&lt;br&gt;
In this post I will show how we can do this using infrastructure as code using Bicep.&lt;/p&gt;
&lt;h2&gt;
  
  
  Presentation of the context
&lt;/h2&gt;

&lt;p&gt;As an example we are going to build a very simple project consisting in the following elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Storage Account&lt;/li&gt;
&lt;li&gt;A table in the Storage Account&lt;/li&gt;
&lt;li&gt;A Logic App triggered by HTTP: each request will add a line in the storage table with the IP of the caller&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/yLo191UNaD4LSlQlbL58v60qgIZB-jOtduP0W8gz7nc/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2xvZ2lj/LWFwcHMtaWFjLzAx/LWRpYWdyYW0ucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/yLo191UNaD4LSlQlbL58v60qgIZB-jOtduP0W8gz7nc/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2xvZ2lj/LWFwcHMtaWFjLzAx/LWRpYWdyYW0ucG5n" alt="Diagram" width="880" height="288"&gt;&lt;/a&gt; &lt;em&gt;The following diagram shows what we are going to create here&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub repository
&lt;/h2&gt;

&lt;p&gt;I have prepared a &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/"&gt;GitHub repository&lt;/a&gt; to let you see the whole code of the demo, and eventually run it by yourself.&lt;br&gt;&lt;br&gt;
The repo contains the full IaC code in Bicep and Terraform, in the post I will show the most relevant parts in Bicep only for clarity reasons (and also because it's all little bit more complicated in Terraform, more on this later).&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating the base resources
&lt;/h2&gt;

&lt;p&gt;To get started we are going to create all the resources but the Logic App itself. If you're not familiar with Bicep this will help to understand the rest of the code, otherwise you can jump to the next section.&lt;br&gt;&lt;br&gt;
Note that my naming convention is composed of the same &lt;em&gt;suffix&lt;/em&gt; in all resources names (with a random part to ensure the unicity of the name), and a &lt;em&gt;prefix&lt;/em&gt; depending on the resource type.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can use &lt;a href="https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-abbreviations"&gt;this page&lt;/a&gt; from the Cloud Adoption Framework as a reference for the abbreviations for Azure resource types.&lt;br&gt;&lt;br&gt;
This &lt;a href="https://justinoconnor.codes/2022/08/19/azure-periodic-table-of-resource-naming-convention-shorthands/"&gt;periodic table of Azure resource types&lt;/a&gt; is also pretty neat.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm new to Bicep but already used to create a &lt;code&gt;main.bicep&lt;/code&gt; module containing a &lt;em&gt;subscription&lt;/em&gt; deployment to create the resource group, and a &lt;code&gt;resources.bicep&lt;/code&gt; module containing a &lt;em&gt;resource group&lt;/em&gt; deployment.&lt;br&gt;&lt;br&gt;
I will focus on the &lt;code&gt;resources.bicep&lt;/code&gt; module here, here is the beginning of the module with the creation of the Storage Account, and the storage table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@description('The suffix to use in resource naming.')
param suffix string
param location string

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-09-01' = {
  name: substring('stor${replace(suffix, '-', '')}', 0, 24)
  location: location

  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}

resource tableStorageService 'Microsoft.Storage/storageAccounts/tableServices@2021-09-01' = {
  name: 'default'
  parent: storageAccount
}

resource storageTable 'Microsoft.Storage/storageAccounts/tableServices/tables@2021-09-01' = {
  name: 'logicAppCalls'
  parent: tableStorageService
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the API Connection
&lt;/h2&gt;

&lt;p&gt;To interact with a resource or a service, a Logic App requires an &lt;em&gt;API connection&lt;/em&gt;, which is basically a wrapper around an API, the Azure table storage API in our case.&lt;br&gt;&lt;br&gt;
This is one of the key steps of this post, as the service connection contains the way the Logic App authenticates to the Storage Account. I could have used an account key but decided to use a &lt;em&gt;managed identity&lt;/em&gt; to follow best practices (&lt;a href="https://medium.com/medialesson/deploying-azure-logic-apps-managed-identity-with-bicep-e1354f185e4d"&gt;this post&lt;/a&gt; helped me a lot to make this part work).  &lt;/p&gt;

&lt;p&gt;Long story short, the trick with the Managed Identity is to use the &lt;code&gt;2018-07-01-preview&lt;/code&gt; API version of the &lt;code&gt;Microsoft.Web/connections&lt;/code&gt; resource type. Bicep will show a warning because of this but it's currently the only way to make it work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource tableStorageConnection 'Microsoft.Web/connections@2018-07-01-preview' = {
  name: 'tableStorage'
  location: location

  properties: {
    parameterValueSet: {
      name: 'managedIdentityAuth'
      values: {}
    }
    api: {
      id: '${subscription().id}/providers/Microsoft.Web/locations/${location}/managedApis/azuretables'
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating the Logic App workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why are Logic Apps &lt;em&gt;different&lt;/em&gt; ?
&lt;/h3&gt;

&lt;p&gt;Before jumping into some more IaC code, let's talk about how teams could manage (develop, version and deploy) Logic Apps and why they are different from other Azure resources like Web Apps or Azure Functions.&lt;br&gt;&lt;br&gt;
Functions and Web Apps are code-first services whose deployment require two steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A provisioning step to create the resource in Azure (using IaC, the CLI, the portal, ...)&lt;/li&gt;
&lt;li&gt;A deployment step to push business code to the resource (using a CD pipeline, VS Code,  a git push, ...)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Basically an empty shell is created, and then content is sent into the shell. Both steps can be achieved by different teams using different technologies.&lt;br&gt;&lt;br&gt;
Also deploying a new version of the business code does not reflect any change on the infrastructure.&lt;/p&gt;

&lt;p&gt;Logic apps is a design-first service, typically you design your workflow in a GUI (like the Azure portal), and it is saved in a JSON "code" tied to the Azure resource.&lt;br&gt;&lt;br&gt;
So there is not the same separation between provisioning and deployment as above: any change to the business logic will be reflected on the infrastructure.&lt;br&gt;&lt;br&gt;
In other words, the "code" or the "logic" of the Logic Apps cannot be deployed separately from the creation of the resource itself.  &lt;/p&gt;
&lt;h3&gt;
  
  
  Let's develop/deploy this Logic App for good now !
&lt;/h3&gt;

&lt;p&gt;So how can we develop Logic Apps from the GUI and automate their deployment using IaC ?&lt;br&gt;&lt;br&gt;
Here is what I've got in the Azure portal once I've finished "developing" my Logic App:&lt;br&gt;
&lt;a href="https://community.ops.io/images/w7UjnKXZ4_GN63pnTfBQlETtCyqYskV53_DpZ5K1lWo/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2xvZ2lj/LWFwcHMtaWFjLzAy/LXdvcmtmbG93LXBv/cnRhbC5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/w7UjnKXZ4_GN63pnTfBQlETtCyqYskV53_DpZ5K1lWo/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2xvZ2lj/LWFwcHMtaWFjLzAy/LXdvcmtmbG93LXBv/cnRhbC5wbmc" alt="Logic App workflow in Azure portal" width="880" height="455"&gt;&lt;/a&gt; &lt;em&gt;A similar experience is available using the VS Code extension&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Switching to the &lt;em&gt;Code view&lt;/em&gt; allows me to see my workflow in JSON. From there I can copy the &lt;code&gt;definition&lt;/code&gt; element and save its content a &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/logic_apps/insertIntoTableStorage.json"&gt;file&lt;/a&gt; in my repo.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why not taking &lt;em&gt;all&lt;/em&gt; the content of the code view ? Because the &lt;code&gt;parameters&lt;/code&gt; element contains the resource id of the API connection (with the subscription id and resource group name) and the Storage Account name, and I don't want these kind of environment-related information to end up in my git repository.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that my logic is saved (and versioned) alongside my IaC code, I can use it in the &lt;code&gt;definition&lt;/code&gt; property of my Logic App workflow (using the &lt;code&gt;loadJsonContent&lt;/code&gt; builtin function):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource logicApp 'Microsoft.Logic/workflows@2019-05-01' = {
  name: 'ala-${suffix}'
  location: location

  identity: {
    type: 'SystemAssigned'
  }

  properties: {
    definition: loadJsonContent('../logic_apps/insertIntoTableStorage.json')
    parameters: {
      '$connections': {
        value: {
          '${connectionApiName}': {
            connectionId: tableStorageConnection.id
            connectionName: connectionApiName
            id: '${subscription().id}/providers/Microsoft.Web/locations/${location}/managedApis/${connectionApiName}'
            connectionProperties: {
              authentication: {
                type: 'ManagedServiceIdentity'
              }
            }
          }
        }
      }
      storageAccountName: {
        value: storageAccount.name
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only thing left to do is to grant the Logic App access to the Storage Account using an assignment to the &lt;code&gt;Storage Table Data Contributor&lt;/code&gt; builtin role. This can be seen &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/bicep/resources.bicep#L73-L83"&gt;here&lt;/a&gt; in the repository.  &lt;/p&gt;

&lt;p&gt;Finally everything can be deployed using a simple &lt;code&gt;az deployment sub create&lt;/code&gt; command.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about Terraform ?
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, this little demo project has been done in Bicep and Terraform, but I ended up keeping only the Bicep version in the blog post. This is for clarity reasons but also because doing this with Terraform has the following drawbacks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;There is no support for API connections in the AzureRM Terraform provider, so I had to create &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/apiConnectionArm.json"&gt;an ARM template&lt;/a&gt; and &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/main.tf#L30-L35"&gt;invoke it&lt;/a&gt; in my Terraform code to create the API connection&lt;/li&gt;
&lt;li&gt;A Logic App workflow can be created using the &lt;code&gt;logic_app_workflow&lt;/code&gt; resource of the AzureRM provider, but it doesn't provide a way to set the definition of the workflow. The solution was to use this resource to create the workflow (with the Managed Identity for later role assignment), and then another ARM template &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/logicAppWorkflowArm.json"&gt;file&lt;/a&gt; to deploy the definition of the Logic App from the Terraform &lt;a href="https://github.com/xaviermignot/deploy-logic-apps-with-iac/blob/81f3560869aaf70bd945132e63b7034833216403/terraform/main.tf#L59-L74"&gt;code&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;This post shows a way to achieve a first step in managing Logic Apps &lt;em&gt;at scale&lt;/em&gt;.&lt;br&gt;&lt;br&gt;
There are still many things to cover such as how to run the deployments in a pipeline, how to make a change and bring it from development to production, how to allow changes using the GUI only in development and force the use of pipelines in other environments, etc.&lt;br&gt;&lt;br&gt;
I hope this post will help you to bring your Logic Apps to the next level, do not hesitate to reach out if you need, and thanks for reading 🤓&lt;/p&gt;

</description>
      <category>azure</category>
      <category>bicep</category>
      <category>logicapps</category>
      <category>iac</category>
    </item>
    <item>
      <title>TLS with Terraform and Azure: use managed certificates</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Wed, 25 May 2022 19:52:41 +0000</pubDate>
      <link>https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-use-managed-certificates-4ghd</link>
      <guid>https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-use-managed-certificates-4ghd</guid>
      <description>&lt;p&gt;This post is the last one of my series on the generation of TLS certificates with Terraform for Azure, after the post about &lt;a href="https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-280j"&gt;self signed certificates&lt;/a&gt; and the one about &lt;a href="https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-get-certificates-from-lets-encrypt-1ne2"&gt;Let's Encrypt&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
For this one we are going to let Azure &lt;em&gt;manage&lt;/em&gt; everything by using &lt;em&gt;managed certificates&lt;/em&gt;, a feature available on several services that let Azure handle the generation and the renewal of certificates.&lt;/p&gt;
&lt;h2&gt;
  
  
  Previously in the "TLS with Terraform and Azure" series...
&lt;/h2&gt;

&lt;p&gt;If you haven't read the previous posts of the series you can check out &lt;a href="https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-280j/#presentation-of-the-context-for-the-whole-series"&gt;this section&lt;/a&gt; of the first one to get the common context for the whole series.  &lt;/p&gt;

&lt;p&gt;There is also a GitHub repository that contains all the code from this series of posts: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-certificates"&gt;
        terraform-certificates
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A repository showing how to generate certificates using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Certificate generation with Terraform for Azure App Service&lt;/h1&gt;
&lt;p&gt;This repository contains sample code to generate TLS certificates using Terraform.&lt;br&gt;
It uses an Azure App Service as an example of a website to secure.&lt;/p&gt;
&lt;p&gt;The certificates are generated in 3 ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;By creating a self-signed certificate&lt;/li&gt;
&lt;li&gt;By requesting a certificate from Let's Encrypt&lt;/li&gt;
&lt;li&gt;By creating an Azure App Service managed certificate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Obviously the third example can only work with Azure. The two others are written to work with Azure as well but can be adapted or used as an inspiration to work on other platforms.&lt;/p&gt;
&lt;h2&gt;
Getting started&lt;/h2&gt;
&lt;h3&gt;
Set the variables of the root module&lt;/h3&gt;
&lt;p&gt;If you want to run this against your Azure infrastructure you will need to provide values for the variables of the root module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_name&lt;/code&gt; should contain the name of your DNS Zone managed in Azure&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_rg_name&lt;/code&gt; should contain the name of the resource group containing…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-certificates"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;To give some context very quickly, let's say we have used the &lt;code&gt;app_service&lt;/code&gt; module of the GitHub repo to create an App Service bound to a custom domain, and now we need to secure this custom domain using a certificate.&lt;br&gt;&lt;br&gt;
We have already done this using a self-signed certificate, and with a Let's Encrypt certificate, now we are doing it using a managed one.&lt;/p&gt;
&lt;h2&gt;
  
  
  Level 3: let Azure handle the certificate stuff
&lt;/h2&gt;

&lt;p&gt;The idea behind managed certificates is pretty simple: if you can prove you &lt;em&gt;own&lt;/em&gt; a domain, then Azure will issue a certificate for you, valid for this domain.&lt;br&gt;&lt;br&gt;
And then you don't have to worry about it, Azure will renew the certificate for you, you can forget about it and focus on other things, like your business for instance...&lt;/p&gt;

&lt;p&gt;On the diagram side, things looks way simpler than for the other "levels":&lt;a href="https://community.ops.io/images/rKwXl8FN01W4g1vMMPhha8Bo2wiHocZ2hQgJwwHNBo4/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDQtbWFuYWdlZC5w/bmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/rKwXl8FN01W4g1vMMPhha8Bo2wiHocZ2hQgJwwHNBo4/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDQtbWFuYWdlZC5w/bmc" alt="Diagram" width="880" height="414"&gt;&lt;/a&gt; &lt;em&gt;The diagram looks almost the same as for self-signed certificates&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On the code side, we have previously bound the App Service to a custom domain using a &lt;code&gt;azurerm_app_service_custom_hostname_binding&lt;/code&gt; resource in the &lt;code&gt;app_service&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Bind app service to custom domain&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_custom_hostname_binding"&lt;/span&gt; &lt;span class="s2"&gt;"app"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;hostname&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tf-certs-demo.${var.dns.zone_name}"&lt;/span&gt;
  &lt;span class="nx"&gt;app_service_name&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_app_service&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;azurerm_dns_txt_record&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In the &lt;code&gt;managed&lt;/code&gt; module, creating the managed is done using the &lt;code&gt;azurerm_app_service_managed_certificate&lt;/code&gt; with only the &lt;em&gt;binding id&lt;/em&gt; as an argument:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_managed_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"managed"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;custom_hostname_binding_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_domain_binding_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And finally, back in the &lt;code&gt;main&lt;/code&gt; module, the &lt;code&gt;azurerm_app_service_certificate_binding&lt;/code&gt; resource brings everything together by binding the managed certificate to the custom domain:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_certificate_binding"&lt;/span&gt; &lt;span class="s2"&gt;"cert_binding"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;certificate_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;managed&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certificate_id&lt;/span&gt;
  &lt;span class="nx"&gt;hostname_binding_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;app_service&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;custom_domain_binding_id&lt;/span&gt;
  &lt;span class="nx"&gt;ssl_state&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"SniEnabled"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note that comparing to the other posts of the series, I have detailed a little bit more what's happening in the code before and after the generation of the certificate. Otherwise I would have only 3 lines of code to show 😬  &lt;/p&gt;

&lt;p&gt;The code snippets above are spread into 3 modules from the repo of the whole series.&lt;br&gt;&lt;br&gt;
I had previously made another repo dedicated to managed certificates using a single module if you prefer: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/app-service-managed-certificate-demo"&gt;
        app-service-managed-certificate-demo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Azure App Service Managed Certificate demo&lt;/h1&gt;
&lt;p&gt;This repository contains a simple demo of App Service managed certificates with Terraform.&lt;/p&gt;
&lt;/div&gt;

  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/app-service-managed-certificate-demo"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;When browsing the App Service, we can see that the certificate is issued by GeoTrust (aka DigiCert) and of course trusted by the browser: &lt;a href="https://community.ops.io/images/4RUbsv-39KetubLjv6-68XYgtkRFdGXtkYQHUFcuHr0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDQtbWFuYWdlZC1i/cm93c2VyLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/4RUbsv-39KetubLjv6-68XYgtkRFdGXtkYQHUFcuHr0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDQtbWFuYWdlZC1i/cm93c2VyLnBuZw" alt="Browser" width="473" height="665"&gt;&lt;/a&gt; &lt;em&gt;The certificate is trusted and valid for 6 months ✅&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed certificates: too good to be true ?
&lt;/h2&gt;

&lt;p&gt;As you can see it's very simple to generate a managed certificate, and it's &lt;del&gt;free&lt;/del&gt; included in the App Service Plan's pricing. It looks like the perfect solution for securing your Web Apps, but there is a downside that you should be aware of: if you want to use managed certificates, &lt;strong&gt;your App Service have to be publicly exposed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So you can't use them if you put your Web Apps behind an appliance like an Application Gateway and enforce the traffic to go through this appliance. &lt;br&gt;
Even if it's technically feasible to do so (by using tricky maneuvers, I have done this but I won't tell more 😅), this is not a target solution as it will prevent the certificate from being renewed by App Service.&lt;/p&gt;

&lt;p&gt;There are other minor limitations like the lack of support of wildcard certificates and the impossibility to export the certificate but it seems fair to me and it did not bother me at all in my personal usage.&lt;/p&gt;

&lt;p&gt;These limitations are listed &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/configure-ssl-certificate?tabs=subdomain%2Cportal#create-a-free-managed-certificate"&gt;here&lt;/a&gt; in the documentation, a little bit quietly so I think it's worth mentioning them.&lt;/p&gt;

&lt;p&gt;To finish on a positive note, these limitations mean that managed certificate might not suit enterprise scenarios where we will probably put a security appliance in front of our App Services.&lt;br&gt;&lt;br&gt;
But for personal projects, or smaller ones, proof of concepts, or sandbox environments, it's really a great feature, as it makes really easy to expose you applications securely on a custom domain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed certificates for other Azure services
&lt;/h3&gt;

&lt;p&gt;Staying on a positive track before closing this post, I have to mention that managed certificates are not limited to App Service in Azure.   &lt;/p&gt;

&lt;p&gt;I have already used them for Azure &lt;a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-custom-ssl?tabs=option-1-default-enable-https-with-a-cdn-managed-certificate#tlsssl-certificates"&gt;CDN&lt;/a&gt; (in GA, supported by the AzureRm Terraform provider) and Azure &lt;a href="https://docs.microsoft.com/en-us/azure/api-management/configure-custom-domain?tabs=managed#domain-certificate-options"&gt;API Management&lt;/a&gt; (currently in public preview, no support in Terraform yet).  &lt;/p&gt;

&lt;p&gt;It might also exist for other services so if your are about to use an Azure service associated with a custom domain, look for the support of managed certificates 😉&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;As this post closes the series on certificate generation with Terraform for Azure, let's recap the whole series in a single table:  &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Generation method&lt;/th&gt;
&lt;th&gt;Ease of use&lt;/th&gt;
&lt;th&gt;Security level&lt;/th&gt;
&lt;th&gt;Summary&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Self-signed certificates&lt;/td&gt;
&lt;td&gt;✅✅&lt;/td&gt;
&lt;td&gt;🔓&lt;/td&gt;
&lt;td&gt;You can start here but consider moving to the other method as soon as you can.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let's Encrypt&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;🔐🔐🔐&lt;/td&gt;
&lt;td&gt;Free &amp;amp; trusted certs, require little maintenance once set-up. &lt;br&gt; A little harder to understand at first but then can be used everywhere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed certificates&lt;/td&gt;
&lt;td&gt;✅✅✅&lt;/td&gt;
&lt;td&gt;🔐🔐🔐&lt;/td&gt;
&lt;td&gt;Use this unless your Web Apps can't be directly and publicly accessible. &lt;br&gt; Or if you are not using Web Apps...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's it for this post, I hope this series has been informative, as this is not the topic I am the most comfortable with (don't forget that I am a developer first, and most of us are scared by certificates 😱).  &lt;/p&gt;

&lt;p&gt;Anyway, there are several ways to automate certificate generation in Azure and in general, and the industry makes it easier that ever, through open initiatives like Let's Encrypt or public cloud specific features like managed certificates.  &lt;/p&gt;

&lt;p&gt;Choose the one that best suits your needs, and thanks for reading 🤓&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>TLS with Terraform and Azure: get certificates from Let's Encrypt</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Wed, 25 May 2022 19:46:42 +0000</pubDate>
      <link>https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-get-certificates-from-lets-encrypt-1ne2</link>
      <guid>https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-get-certificates-from-lets-encrypt-1ne2</guid>
      <description>&lt;p&gt;Following my &lt;a href="https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-280j"&gt;previous post&lt;/a&gt; on generating self-signed certificates with Terraform, this one is the second post of the series.&lt;br&gt;&lt;br&gt;
This time we are going to use Let's Encrypt as the certificate authority (CA) instead of our own machine. As a result we will get trusted certificates that can be used in production, for free.&lt;/p&gt;
&lt;h2&gt;
  
  
  Previously in the "TLS with Terraform and Azure" series...
&lt;/h2&gt;

&lt;p&gt;If you haven't read my previous post you can check out &lt;a href="https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-280j/#presentation-of-the-context-for-the-whole-series"&gt;this section&lt;/a&gt; to get the common context for the whole series.&lt;br&gt;&lt;br&gt;
There is also a GitHub repository that contains all the code from this series of posts: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-certificates"&gt;
        terraform-certificates
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A repository showing how to generate certificates using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Certificate generation with Terraform for Azure App Service&lt;/h1&gt;
&lt;p&gt;This repository contains sample code to generate TLS certificates using Terraform.&lt;br&gt;
It uses an Azure App Service as an example of a website to secure.&lt;/p&gt;
&lt;p&gt;The certificates are generated in 3 ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;By creating a self-signed certificate&lt;/li&gt;
&lt;li&gt;By requesting a certificate from Let's Encrypt&lt;/li&gt;
&lt;li&gt;By creating an Azure App Service managed certificate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Obviously the third example can only work with Azure. The two others are written to work with Azure as well but can be adapted or used as an inspiration to work on other platforms.&lt;/p&gt;
&lt;h2&gt;
Getting started&lt;/h2&gt;
&lt;h3&gt;
Set the variables of the root module&lt;/h3&gt;
&lt;p&gt;If you want to run this against your Azure infrastructure you will need to provide values for the variables of the root module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_name&lt;/code&gt; should contain the name of your DNS Zone managed in Azure&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_rg_name&lt;/code&gt; should contain the name of the resource group containing…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-certificates"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
  
&lt;h2&gt;
  
  
  Level 2: requesting a certificate from Let's Encrypt
&lt;/h2&gt;

&lt;p&gt;If you don't know what Let's Encrypt is, in a few words it's a free, automated and open certificate authority (CA), allowing everyone to get certificates trusted by browsers at no charge.&lt;br&gt;&lt;br&gt;
As the Let's Encrypt certificates are valid for 90 days (instead of one year for commercial authorities), automation is strongly recommended.&lt;br&gt;&lt;br&gt;
So how can we automate this with Terraform ? Using the third-party provider &lt;a href="https://registry.terraform.io/providers/vancluever/acme/latest/docs"&gt;ACME&lt;/a&gt;. ACME stands for &lt;em&gt;Automated Certificate Management Environment&lt;/em&gt;, the protocol used by Let's Encrypt. The Terraform ACME provider supports any ACME CA, so we need to configure Let's Encrypt's endpoint in the provider configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# The provider is declared here just like any provider...&lt;/span&gt;
    &lt;span class="nx"&gt;acme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"vancluever/acme"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# ...and is configured here, with the Let's Encrypt production endpoint.&lt;/span&gt;
&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"acme"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;server_url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://acme-v02.api.letsencrypt.org/directory"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's Encrypts provides a staging endpoint you should use for testing: &lt;code&gt;https://acme-staging-v02.api.letsencrypt.org/directory&lt;/code&gt;&lt;br&gt;&lt;br&gt;
There is a limit of 50 certificates per domain and per week on the production environment, on the staging it's 30,000 (but the certificates will not be trusted by the browser)&lt;/p&gt;

&lt;p&gt;Once the provider properly configured, we can start creating resources. As for self-signed certificates, it starts here with a private key, and using this private key and an email we create a &lt;em&gt;registration&lt;/em&gt; which is an account on the ACME server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creates a private key in PEM format&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_private_key"&lt;/span&gt; &lt;span class="s2"&gt;"private_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RSA"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Creates an account on the ACME server using the private key and an email&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"acme_registration"&lt;/span&gt; &lt;span class="s2"&gt;"reg"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;account_key_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_pem&lt;/span&gt;
  &lt;span class="nx"&gt;email_address&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can request a certificate using our account, this is where the real magic happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# As the certificate will be generated in PFX a password is required&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Gets a certificate from the ACME server&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"acme_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;account_key_pem&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acme_registration&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reg&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_key_pem&lt;/span&gt;
  &lt;span class="nx"&gt;common_name&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;common_name&lt;/span&gt; &lt;span class="c1"&gt;# The hostname goes here&lt;/span&gt;
  &lt;span class="nx"&gt;certificate_p12_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;

  &lt;span class="nx"&gt;dns_challenge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Many providers are supported for the DNS challenge, we are using Azure DNS here&lt;/span&gt;
    &lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"azure"&lt;/span&gt;

    &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Some arguments are passed here but it's not enough to let the provider access the zone in Azure DNS.&lt;/span&gt;
      &lt;span class="c1"&gt;# Other arguments (tenant id, subscription id, and cient id/secret) must be set through environment variables.&lt;/span&gt;
      &lt;span class="nx"&gt;AZURE_RESOURCE_GROUP&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_rg_name&lt;/span&gt;
      &lt;span class="nx"&gt;AZURE_ZONE_NAME&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dns&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;zone_name&lt;/span&gt;
      &lt;span class="nx"&gt;AZURE_TTL&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key part is in the &lt;code&gt;dns_challenge&lt;/code&gt; block of the &lt;code&gt;acme_certificate&lt;/code&gt; resource.  &lt;/p&gt;

&lt;p&gt;Before generating a certificate for our domain, Let's Encrypt checks that we &lt;em&gt;own&lt;/em&gt; that domain. To do that it proceeds with a DNS &lt;em&gt;challenge&lt;/em&gt;, basically it generates a random string and will not generate the certificate unless that random string is in a specific TXT record of the DNS zone.  &lt;/p&gt;

&lt;p&gt;Obviously the ACME provider does that for us, we just need to let it access our DNS zone. The provider supports &lt;em&gt;many&lt;/em&gt; DNS providers, in this case we are using Azure DNS.  &lt;/p&gt;

&lt;p&gt;So we need to tell the ACME provider &lt;em&gt;where&lt;/em&gt; our DNS zone is, so we specify its name and resource group name in the &lt;code&gt;config&lt;/code&gt; block above. Unfortunately the provider cannot use the Azure CLI authentication so we need to set additional arguments for authentication, so that the provider knows the subscription we are using, and a client id/secret to access it.  &lt;/p&gt;

&lt;p&gt;This is the kind of information that should not be pushed to a git repository, so I prefer to put these as environment variables in a &lt;code&gt;.env&lt;/code&gt; git-ignored file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_TENANT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR AZURE TENAND ID (a guid)&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_SUBSCRIPTION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOUR AZURE SUBSCRIPTION ID (another guid)&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;AN APP REGISTRATION ID (yep it's a guid too)&amp;gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ARM_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;THE APP REGISTRATION SECRET (not a guid this time)&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I use the &lt;code&gt;source .env&lt;/code&gt; command and the ACME provider can use the environment variables to authenticate to Azure and add/remove records in my DNS zone.  &lt;/p&gt;

&lt;p&gt;If you are using Terraform Cloud or running Terraform in a CI/CD context you will not need to do this as the environment variables are already set  &lt;/p&gt;

&lt;p&gt;Once the &lt;a href="https://github.com/xaviermignot/terraform-certificates/blob/main/02_acme/main.tf"&gt;full code&lt;/a&gt; has been applied you can check out the Activity Log of your DNS zone.&lt;br&gt;&lt;br&gt;
You should see that the service principal corresponding to the environment variables has created and deleted a TXT record in the zone :&lt;br&gt;
&lt;a href="https://community.ops.io/images/vkHUMZL2Hg9r2c0KE4tZUJX4T5G3l5YTCyoAOMYq1NA/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDMtbGV0cy1lbmNy/eXB0LWFjdGl2aXR5/LWxvZy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/vkHUMZL2Hg9r2c0KE4tZUJX4T5G3l5YTCyoAOMYq1NA/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDMtbGV0cy1lbmNy/eXB0LWFjdGl2aXR5/LWxvZy5wbmc" alt="Activity Log" width="880" height="94"&gt;&lt;/a&gt; &lt;em&gt;The creation and deletion of the TXT record should occur within a minute&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And more importantly you can browse your App Service from its custom hostname and see how your browser enjoys this fresh trusted certificate:&lt;br&gt;&lt;br&gt;
&lt;a href="https://community.ops.io/images/qKFgJ5Pjtq9uc6d6IfM_FIiHCWIcK4ttllNr1uMHyYM/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDMtbGV0cy1lbmNy/eXB0LWJyb3dzZXIu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/qKFgJ5Pjtq9uc6d6IfM_FIiHCWIcK4ttllNr1uMHyYM/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDMtbGV0cy1lbmNy/eXB0LWJyb3dzZXIu/cG5n" alt="Browser" width="834" height="1058"&gt;&lt;/a&gt; &lt;em&gt;Note the R3 issuer which is Let's Encrypt, and the absence of security warning ✅&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's if for this post, which is the one why I have started this series. At first I had trouble understanding how this Let's Encrypt/ACME provider works and didn't find any content showing a full example. &lt;br&gt;
So I hope this post makes sense and helps other to issue trusted certificates using Terraform.  &lt;/p&gt;

&lt;p&gt;Don't hesitate to dive into Let's Encrypt documentation as well, they explain in detail how the &lt;a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge"&gt;DNS challenge&lt;/a&gt; works, as well as the &lt;a href="https://letsencrypt.org/how-it-works/"&gt;service itself&lt;/a&gt; and their &lt;a href="https://letsencrypt.org/certificates/"&gt;certificate chain&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Back to the original diagram, here is where we are now:&lt;br&gt;&lt;br&gt;
&lt;a href="https://community.ops.io/images/LYjFZm9m-4AWmjEMIbek4MMIAmwuZtJmujnDjEIQov0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDMtbGV0cy1lbmNy/eXB0LnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/LYjFZm9m-4AWmjEMIbek4MMIAmwuZtJmujnDjEIQov0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDMtbGV0cy1lbmNy/eXB0LnBuZw" alt="Diagram" width="880" height="605"&gt;&lt;/a&gt; &lt;em&gt;The certificate is now issued by a trusted CA before being bounded to the App Service&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The next post will close the series with Azure &lt;em&gt;managed&lt;/em&gt; certificates, a feature that deserves more attention 😉&lt;br&gt;&lt;br&gt;
Thanks for reading !&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>TLS with Terraform and Azure: generate self-signed certificates</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Wed, 25 May 2022 19:39:14 +0000</pubDate>
      <link>https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-280j</link>
      <guid>https://community.ops.io/xaviermignot/tls-with-terraform-and-azure-generate-self-signed-certificates-280j</guid>
      <description>&lt;p&gt;We all love starting pet projects, so we tend to buy custom domains as it can be fairly cheap. SSL/TLS certificates on the other hand used to be pricey, but today there are several solutions to get these for free.&lt;br&gt;&lt;br&gt;
And this is for the good cause as every website should be secured by certificates nowadays.&lt;br&gt;&lt;br&gt;
This post is the first of a series where I will share 3 ways to automate the generation of certificates with Terraform for your Azure projects.&lt;br&gt;&lt;br&gt;
This one introduces the common workflow around an Azure Web App, and shows the first &lt;em&gt;level&lt;/em&gt; of certificate generation using &lt;em&gt;self-signed&lt;/em&gt; certificates.&lt;br&gt;&lt;br&gt;
The second post is using Let's Encrypt and the last one managed certificates.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Presentation of the context for the whole series
&lt;/h2&gt;

&lt;p&gt;Let's say we have the following architecture as a starting point:&lt;br&gt;&lt;br&gt;
&lt;a href="https://community.ops.io/images/6ZsylVlkQ4UbmCIIgX5fiQ-Uoqs23OkGlVqznDEXcKo/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDEtaHR0cC5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/6ZsylVlkQ4UbmCIIgX5fiQ-Uoqs23OkGlVqznDEXcKo/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDEtaHR0cC5wbmc" alt="Starting architecture" width="880" height="414"&gt;&lt;/a&gt; &lt;em&gt;Let's start with a Web App bound to a custom domain&lt;/em&gt;&lt;br&gt;&lt;br&gt;
So we have the following components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An App Service running in a plan with in the Basic tier at least&lt;/li&gt;
&lt;li&gt;A DNS zone with at least the following records:

&lt;ul&gt;
&lt;li&gt;A CNAME record pointing to the default App Service hostname (&lt;code&gt;*.azurewebsites.net&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A TXT records to verify the domain ownership&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;These two records allow the creation of a custom hostname &lt;em&gt;binding&lt;/em&gt; at the App Service level&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is created using Terraform CLI from my terminal, including the DNS records, that's why I'm using Azure DNS 😎&lt;br&gt;&lt;br&gt;
With this setup the App Service is accessible on the custom hostname using HTTP only, if we try to access it using HTTPS our browser displays an error as the only certificate it can find is bound the &lt;code&gt;azurewebsites.net&lt;/code&gt; domain:&lt;br&gt;&lt;br&gt;
&lt;a href="https://community.ops.io/images/ZinbngZa1Q6uZPr0O2DKF-HNOG2IttQzDVeYoP-W69g/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDEtaHR0cC1icm93/c2VyLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ZinbngZa1Q6uZPr0O2DKF-HNOG2IttQzDVeYoP-W69g/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDEtaHR0cC1icm93/c2VyLnBuZw" alt="Browser error" width="482" height="677"&gt;&lt;/a&gt; &lt;em&gt;The browser displays an error as the certificate doesn't match the hostname... yet&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;In the Azure portal, the custom domain is added to the App Service but marked as unsecured:&lt;br&gt;
&lt;a href="https://community.ops.io/images/QwkmanaCoD0g3TFexMBV4Y7Nvilc6JnIUX5JMJhJ4L4/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDEtaHR0cC1wb3J0/YWwucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/QwkmanaCoD0g3TFexMBV4Y7Nvilc6JnIUX5JMJhJ4L4/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDEtaHR0cC1wb3J0/YWwucG5n" alt="Azure portal" width="638" height="212"&gt;&lt;/a&gt; &lt;em&gt;The Custom domains blade of the Azure portal displays this error on our custom domain&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now that we have our basic setup, let's see how we can secure this web app.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub repository
&lt;/h2&gt;

&lt;p&gt;All the code from this series of posts is available in the following repo: &lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/xaviermignot"&gt;
        xaviermignot
      &lt;/a&gt; / &lt;a href="https://github.com/xaviermignot/terraform-certificates"&gt;
        terraform-certificates
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A repository showing how to generate certificates using Terraform
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;h1&gt;
Certificate generation with Terraform for Azure App Service&lt;/h1&gt;
&lt;p&gt;This repository contains sample code to generate TLS certificates using Terraform.&lt;br&gt;
It uses an Azure App Service as an example of a website to secure.&lt;/p&gt;
&lt;p&gt;The certificates are generated in 3 ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;By creating a self-signed certificate&lt;/li&gt;
&lt;li&gt;By requesting a certificate from Let's Encrypt&lt;/li&gt;
&lt;li&gt;By creating an Azure App Service managed certificate&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Obviously the third example can only work with Azure. The two others are written to work with Azure as well but can be adapted or used as an inspiration to work on other platforms.&lt;/p&gt;
&lt;h2&gt;
Getting started&lt;/h2&gt;
&lt;h3&gt;
Set the variables of the root module&lt;/h3&gt;
&lt;p&gt;If you want to run this against your Azure infrastructure you will need to provide values for the variables of the root module:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_name&lt;/code&gt; should contain the name of your DNS Zone managed in Azure&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;dns_zone_rg_name&lt;/code&gt; should contain the name of the resource group containing…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/xaviermignot/terraform-certificates"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;&lt;br&gt;
The &lt;code&gt;readme&lt;/code&gt; explains how to get started if you want to create the resources and generate the certificates in your own subscription.
&lt;h2&gt;
  
  
  Level 1: generating a self-signed certificate
&lt;/h2&gt;

&lt;p&gt;As a very first step we will generate a &lt;em&gt;self-signed&lt;/em&gt; certificate, something we will not do for production but can be handy for a quick test.&lt;br&gt;&lt;br&gt;
HashiCorp provides a &lt;a href="https://registry.terraform.io/providers/hashicorp/tls/latest"&gt;tls&lt;/a&gt; provider to generate the certificate using Terraform just like we will do using openssl in Linux or PowerShell in Windows.&lt;br&gt;&lt;br&gt;
Here is the HCL code to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Creates a private key in PEM format&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_private_key"&lt;/span&gt; &lt;span class="s2"&gt;"private_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;algorithm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RSA"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Generates a TLS self-signed certificate using the private key&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"tls_self_signed_cert"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;key_algorithm&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;algorithm&lt;/span&gt;
  &lt;span class="nx"&gt;private_key_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_pem&lt;/span&gt;

  &lt;span class="nx"&gt;validity_period_hours&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;

  &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# The subject CN field here contains the hostname to secure&lt;/span&gt;
    &lt;span class="nx"&gt;common_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;common_name&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;allowed_uses&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"key_encipherment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"digital_signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"server_auth"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's just two resources: a private key and a certificate generated using this private key.&lt;br&gt;&lt;br&gt;
We could stop here but in an Microsoft context we need to export the certificate in PFX format to use it in an Azure service such as App Service or Application Gateway.&lt;br&gt;&lt;br&gt;
This is done by combining the use of the HashiCorp &lt;a href="https://registry.terraform.io/providers/hashicorp/random/latest"&gt;random&lt;/a&gt; provider (to generate a password) and the third-party provider &lt;a href="https://registry.terraform.io/providers/chilicat/pkcs12/latest"&gt;pkcs12&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# To convert the PEM certificate in PFX we need a password&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"random_password"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;length&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;
  &lt;span class="nx"&gt;special&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# This resource converts the PEM certicate in PFX&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"pkcs12_from_pem"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cert_pem&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_self_signed_cert&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cert_pem&lt;/span&gt;
  &lt;span class="nx"&gt;private_key_pem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tls_private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key_pem&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;random_password&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Finally we push the PFX certificate in the Azure webspace&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_app_service_certificate"&lt;/span&gt; &lt;span class="s2"&gt;"self_signed_cert"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"self-signed"&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;

  &lt;span class="nx"&gt;pfx_blob&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkcs12_from_pem&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;
  &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pkcs12_from_pem&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;self_signed_cert&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it, the certificate is ready to be used as an App Service certificate as you can see in the full code &lt;a href="https://github.com/xaviermignot/terraform-certificates/blob/main/01_self_signed/main.tf"&gt;here&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;As you have probably guessed this certificate will not make the browser happy as our machine is not recognized as a trusted authority:&lt;br&gt;&lt;br&gt;
&lt;a href="https://community.ops.io/images/WHeu7TjT3o8Fw_OOEs8wCT-OKxdUKoPRXNBtbzfNDjI/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDItc2VsZi1zaWdu/ZWQtYnJvd3Nlci5w/bmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/WHeu7TjT3o8Fw_OOEs8wCT-OKxdUKoPRXNBtbzfNDjI/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDItc2VsZi1zaWdu/ZWQtYnJvd3Nlci5w/bmc" alt="Browser error" width="486" height="667"&gt;&lt;/a&gt; &lt;em&gt;The hostname matches but the browser still displays an error as the authority is not trusted&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But at least the Azure portal is happier as it considers that the custom domain is now secured:&lt;br&gt;
&lt;a href="https://community.ops.io/images/RRpTtALObG6Cw-bhjI6d4xw-ShlTjmR2po5VNysCCZY/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDItc2VsZi1zaWdu/ZWQtcG9ydGFsLnBu/Zw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/RRpTtALObG6Cw-bhjI6d4xw-ShlTjmR2po5VNysCCZY/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDItc2VsZi1zaWdu/ZWQtcG9ydGFsLnBu/Zw" alt="Azure portal" width="657" height="205"&gt;&lt;/a&gt; &lt;em&gt;The Azure portal seems less picky than the browser...&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;That's it for this first step, which serves as an introduction to next posts. As I already mentioned the use of self-signed certificates is not recommended for production, and next you will see we can do much better than that.&lt;br&gt;&lt;br&gt;
If we take a look back to our initial diagram, we have made the following changes:&lt;br&gt;
&lt;a href="https://community.ops.io/images/MStmRsSTcqyBunCC41lrIWXDftEDSx99p8qYzN6yrr0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDItc2VsZi1zaWdu/ZWQucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/MStmRsSTcqyBunCC41lrIWXDftEDSx99p8qYzN6yrr0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL2F6dXJl/LXRlcnJhZm9ybS1j/ZXJ0aWZpY2F0ZXMv/MDItc2VsZi1zaWdu/ZWQucG5n" alt="Generating self-signed certs" width="880" height="414"&gt;&lt;/a&gt; &lt;em&gt;Diagram of the generation and deployment of the self-signed cert&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Stay tuned for the next post who will be about Let's Encrypt. If you already need it, you can go to my &lt;a href="https://github.com/xaviermignot/terraform-certificates"&gt;repo&lt;/a&gt; as the code is already there 🤓&lt;br&gt;&lt;br&gt;
Thanks for reading !&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>devops</category>
    </item>
    <item>
      <title>Use Terraform Cloud for your pet projects</title>
      <dc:creator>Xavier Mignot</dc:creator>
      <pubDate>Wed, 25 May 2022 19:26:26 +0000</pubDate>
      <link>https://community.ops.io/xaviermignot/use-terraform-cloud-for-your-pet-projects-342e</link>
      <guid>https://community.ops.io/xaviermignot/use-terraform-cloud-for-your-pet-projects-342e</guid>
      <description>&lt;p&gt;Over the last few years I have been interested by IaC (Infrastructure as Code), and started recently to use Terraform in my professional life. To keep practicing my Terraform skills I also started to use it for POCs and personal projects instead of using the portal.&lt;br&gt;&lt;br&gt;
Even if I had to push myself at the beginning, I now have a routine to initialize and manage my Azure resources that I will share in this post. It relies on Terraform Cloud that you can use for free !&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Terraform and Terraform Cloud ?
&lt;/h2&gt;

&lt;p&gt;This post is not a deep dive into Terraform, we will stick to the basics. But if you are very new to Terraform, I encourage you to browse the product &lt;a href="https://www.terraform.io/"&gt;website&lt;/a&gt; and follow a &lt;a href="https://learn.hashicorp.com/terraform"&gt;basic tutorial&lt;/a&gt; as it's the best way to know what the tool is about.&lt;br&gt;&lt;br&gt;
To better understand the differences between Terraform and Terraform Cloud, let's sum up the differences real quick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform is the open-source CLI tool with the &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;apply&lt;/code&gt;, &lt;code&gt;destroy&lt;/code&gt; commands that can run anywhere (including on your machine or in Terraform Cloud)&lt;/li&gt;
&lt;li&gt;Terraform Cloud is a SaaS offering to manage Terraform runs and states remotely&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Is it free ?
&lt;/h3&gt;

&lt;p&gt;Terraform CLI is &lt;a href="https://github.com/hashicorp/terraform"&gt;open-source&lt;/a&gt; and free to use. Terraform Cloud is also free for teams up to 5 users, so it's perfect for pet projects.&lt;br&gt;&lt;br&gt;
Terraform Cloud has paid plans for larger organizations, and also a self-hosted version called Terraform Enterprise. That's not the topic of this post, but it's important to know how the company behind the tool makes money.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create your Terraform Cloud workspace
&lt;/h2&gt;

&lt;p&gt;Let's get finally to the point, shall we ? For this sample we will create an Azure Function App which will result in creating the following resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A resource group&lt;/li&gt;
&lt;li&gt;An App Service Plan&lt;/li&gt;
&lt;li&gt;A storage account&lt;/li&gt;
&lt;li&gt;A Function App&lt;/li&gt;
&lt;li&gt;An Application Insights account (optional but always handy to monitor the app's telemetry)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To do this we will need the following prerequisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Terraform Cloud account with an organization, to create one follow the guidelines from &lt;a href="https://learn.hashicorp.com/tutorials/terraform/cloud-sign-up?in=terraform/cloud-get-started#create-an-account"&gt;here&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An Azure subscription, a &lt;a href="https://azure.microsoft.com/en-us/free/"&gt;free&lt;/a&gt; one will be enough&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/cli/azure/"&gt;Azure CLI&lt;/a&gt; installed and connected to your subscription&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Create your workspace
&lt;/h3&gt;

&lt;p&gt;Once you have your &lt;em&gt;organization&lt;/em&gt; (which represents your &lt;em&gt;team&lt;/em&gt;) in Terraform Cloud, you will need a &lt;em&gt;workspace&lt;/em&gt; (which represents your &lt;em&gt;project&lt;/em&gt;).&lt;br&gt;&lt;br&gt;
Go to &lt;a href="https://app.terraform.io/"&gt;Terraform Cloud&lt;/a&gt;, select you organization, and click on the &lt;em&gt;"&lt;strong&gt;New workspace&lt;/strong&gt;"&lt;/em&gt; button.  &lt;/p&gt;

&lt;p&gt;On the next page you have to choose a workflow, choose &lt;em&gt;CLI-driven workflow&lt;/em&gt;:&lt;br&gt;&lt;br&gt;
&lt;a href="https://community.ops.io/images/gT2hiVTjczsbensmjqIQo9ls8F6P8mNH1mbu0-3c_vs/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/Mi13b3Jrc3BhY2Ut/dHlwZS5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/gT2hiVTjczsbensmjqIQo9ls8F6P8mNH1mbu0-3c_vs/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/Mi13b3Jrc3BhY2Ut/dHlwZS5wbmc" alt="CLI-driven workflow" width="880" height="172"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The CLI-driven workflow on the new workspace page&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Using this workflow allows you to run Terraform commands from your machine, but they will run remotely in Terraform Cloud. It's the best of two worlds approach as you get the simplicity of running the commands from your terminal of choice, and you get the security of having the state stored remotely, and the runs are accessible in Terraform Cloud.&lt;/p&gt;
&lt;h3&gt;
  
  
  Give Terraform Cloud access to your Azure subscription
&lt;/h3&gt;

&lt;p&gt;Next you need to connect your workspace with you Azure subscription. Start by creating a new service principal with this Azure CLI command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;az ad sp create-for-rbac &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;SERVICE PRINCIPAL NAME&amp;gt;'&lt;/span&gt; &lt;span class="nt"&gt;--role&lt;/span&gt; Owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choosing a relevant name will be more helpful than the default &lt;code&gt;azure-cli-datetime&lt;/code&gt; name. Specifying the &lt;code&gt;Owner&lt;/code&gt; role is needed if you plan to assign a role to a resource, using a Managed Identity for instance.&lt;/p&gt;

&lt;p&gt;The command will output a JSON object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;A NEW GUID&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"displayName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service-principal-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://service-principal-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;A SUPER SECRET STRING&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tenant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;YOU TENANT ID&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then back in Terraform Cloud you need to create &lt;em&gt;environment variables&lt;/em&gt; from the &lt;em&gt;Variables&lt;/em&gt; tab of your workspace page:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ARM_CLIENT_ID&lt;/code&gt;: The Service Principal's id if from the previous command's output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ARM_CLIENT_SECRET&lt;/code&gt;: The Service Principal's secret also from the command output (you should tick the &lt;em&gt;Sensitive&lt;/em&gt;  checkbox for this one)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ARM_SUBSCRIPTION_ID&lt;/code&gt;: Your subscription id, you can get it with this command: &lt;code&gt;az account show --query id -o tsv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ARM_TENANT_ID&lt;/code&gt;: Your tenant id, also from the command output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should get something like this:&lt;br&gt;
&lt;a href="https://community.ops.io/images/vabobdccORCOVcyp2VytOzvemUsjG-aVnSWvZtn_ybI/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/My1lbnZpcm9ubWVu/dC12YXJpYWJsZXMu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/vabobdccORCOVcyp2VytOzvemUsjG-aVnSWvZtn_ybI/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/My1lbnZpcm9ubWVu/dC12YXJpYWJsZXMu/cG5n" alt="Environment variables" width="880" height="377"&gt;&lt;/a&gt;&lt;em&gt;These variables are then used by Terraform to get tokens for calling the Azure REST API.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Set a few variables for your project
&lt;/h3&gt;

&lt;p&gt;Still from the &lt;em&gt;Variables&lt;/em&gt; tab of your workspace page, you need to create the following &lt;em&gt;Terraform variables&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;project&lt;/code&gt; contains a string used in the names of the Azure resources. It has to be something that will make the names unique globally&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;location&lt;/code&gt; contains the name of the Azure region you want to use, in az cli style such as &lt;code&gt;eastus&lt;/code&gt;, &lt;code&gt;westus&lt;/code&gt;, &lt;code&gt;westeurope&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that these variables are not &lt;em&gt;environment&lt;/em&gt; variables, they are not used in the shell but in the Terraform code.&lt;/p&gt;

&lt;p&gt;Here is how I have set the variables in my workspace:&lt;br&gt;
&lt;a href="https://community.ops.io/images/womi_N1PC4UUN5-Ev362yNEk5X2vFQVHRudPjSo1nN0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/NC10ZXJyYWZvcm0t/dmFyaWFibGVzLnBu/Zw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/womi_N1PC4UUN5-Ev362yNEk5X2vFQVHRudPjSo1nN0/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/NC10ZXJyYWZvcm0t/dmFyaWFibGVzLnBu/Zw" alt="Terraform variables" width="880" height="267"&gt;&lt;/a&gt;&lt;em&gt;My resources will be created in the France Central Azure region, and called afa-xmi-tf-sample, ai-xmi-tf-sample, etc.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Get the code &amp;amp; get ready to deploy
&lt;/h2&gt;

&lt;p&gt;I have made up a GitHub repo with the resources listed above, you can grab it &lt;a href="https://github.com/xaviermignot/terraform-sample"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;
So you don't have to write the Terraform code for now but you can explore it and tweak it as you want.&lt;/p&gt;
&lt;h3&gt;
  
  
  Repository content
&lt;/h3&gt;

&lt;p&gt;For simplicity I have put all the files at the root of the repository. In a "full" project I would have several folders dedicated to the Terraform files, the source, the tests, the doc, etc.&lt;br&gt;&lt;br&gt;
But there is only Terraform files here so it's pretty simple, we can just note that we have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;az-*.tf&lt;/code&gt; files containing the creation of the Azure resources&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tf-*.ft&lt;/code&gt; files containing Terraform configuration such as the variables declaration and the required providers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Add the &lt;code&gt;tf-backend.tf&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;There is one file that you need to add to establish the link between the repo and your Terraform Cloud workspace. Just create the &lt;code&gt;tf-backend.tf&lt;/code&gt; file with the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"remote"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;organization&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR ORGANIZATION NAME&amp;gt;"&lt;/span&gt;

    &lt;span class="nx"&gt;workspaces&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;YOUR WORKSPACE NAME&amp;gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file is git-ignored as it contains the name of the Terraform Cloud organization and workspace. Even if those are not secrets, I consider as good practice not to commit environment-related values like this, especially in public repositories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's deploy the resources !
&lt;/h3&gt;

&lt;p&gt;We are finally getting to the point where we are going to deploy some stuff in Azure. Let's jump in your favorite terminal in the root of the repo if you are not already there, and fire a few commands.&lt;br&gt;&lt;br&gt;
First and just once run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform login&lt;/code&gt; to authenticate to your Terraform Cloud workspace using your browser&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform init&lt;/code&gt; to install the required providers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you are ready to use the most common Terraform CLI commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; to create a &lt;em&gt;plan&lt;/em&gt; to let Terraform determine the changes to make on your infrastructure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply&lt;/code&gt; to &lt;em&gt;apply&lt;/em&gt; the plan and finally make the changes (which will result in the creation of all resources when you run this for the first time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After that feel free to tweak the Terraform files, experiment, and run the &lt;code&gt;plan&lt;/code&gt; and &lt;code&gt;apply&lt;/code&gt; commands again. Use the Azure portal to see what you have deployed:&lt;br&gt;
&lt;a href="https://community.ops.io/images/ifZ3ArnWVmmbsv2NGWFC4re2twfm4Kwgyvw4fFcIUOI/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/NS1henVyZS1wb3J0/YWwucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ifZ3ArnWVmmbsv2NGWFC4re2twfm4Kwgyvw4fFcIUOI/w:880/mb:500000/ar:1/aHR0cHM6Ly9ibG9n/LnhtaS5mci9hc3Nl/dHMvaW1nL3RlcnJh/Zm9ybS1jbG91ZC8w/NS1henVyZS1wb3J0/YWwucG5n" alt="Azure portal" width="880" height="232"&gt;&lt;/a&gt;&lt;em&gt;You should get something similar to this&lt;/em&gt;&lt;br&gt;&lt;br&gt;
Once you are done, you can delete all the resources with the &lt;code&gt;terraform destroy&lt;/code&gt; command.  &lt;/p&gt;

&lt;p&gt;What I like about this approach is that even if you can see the output of the commands in you terminal, everything is running in the cloud, and the state of the infrastructure is securely stored in the cloud as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up &amp;amp; next steps
&lt;/h2&gt;

&lt;p&gt;We have seen in this post how to deploy resources using the CLI approach of Terraform Cloud. While it might seem overkill to do this for personal projects or POCs, I think it's a good compromise as it combines the good practice to have the state securely stored in the cloud with the comfort of running the commands from my terminal (and not to have to push a change to trigger a CI/CD pipeline).&lt;br&gt;&lt;br&gt;
When I finish late my work on a project, I also like to run &lt;code&gt;terraform destroy&lt;/code&gt;, approve the changes and close my laptop right away, as everything is running somewhere else... in the cloud !  &lt;/p&gt;

&lt;p&gt;This post describes a first step in my Terraform journey, and a big one as I needed quite some time to make up this post 😇&lt;br&gt;&lt;br&gt;
Then I might go further with the following next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automate things&lt;/strong&gt;: I could write a script to create the workspace, the service principal and configure everything in a single step&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage environments&lt;/strong&gt;: while it's not necessary in the early stages of pet projects, being able to manage several environments (DEV, PROD, ...) could be interesting to do, and useful for my professional life&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improve security&lt;/strong&gt;: creating a service principal with the Owner role does not following the principle of least privilege... there must be a way to ensure that its access are scoped to the dedicated resource group&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Bicep&lt;/strong&gt;: not related to Terraform at all but I know I will try this new way of deploying stuff in Azure some day 😅&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it for this post, thanks for reading, don't hesitate to reach out, and happy &lt;em&gt;terraforming&lt;/em&gt; 🤓&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>azure</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
