Firewall as Code

Firewall as Code

Introduction

After deploying your Azure Firewall, let see how to manage your firewall rules as code using git and Terraform.

Infrastructure as Code (IaC) is the management of infrastructure (in our case firewall and firewall rules) in a descriptive model, using versioned source code. Like the principle that the same source code generates the same binary, an IaC model generates the same environment every time it is applied. IaC is a key DevOps practice and is used in conjunction with continuous delivery.

In this example we will use GitLab for the code management, Terraform for infrastructure management and Gitlab CI/CD for the deployment.

GitLab + Terraform  + GitLab CI/CD = IaC

  • GitLab is an open source code repository and collaborative software development platform. GitLab offers a location for code storage and capabilities for issue tracking.
  • Terraform is an infrastructure as code tool that allows you to build, change, and version infrastructure safely and efficiently.
  • GitLab CI/CD service is a part of GitLab that build and test the software whenever developer pushes code to application.

To summarize, we will write Terraform code and store it in GitLab and after review it will be automatically deploy with GitLab CI/CD. As one picture is worth a thousand words:

How to manage Firewall rule with Terraform ?

With Azure Firewall, the rules are managed by a Firewall Policy.
A Firewall Policy is an Azure resource that contains NAT, network, and application rule collections and Threat Intelligence settings.

  • One Firewall Policy contains one or more Firewall Policy Rule Collection Group.
  • One Firewall Policy Rule Collection Group contains one or more Rule Collection.
  • One Rule Collection contains one or more Rule.

Let's take a simple example in Terraform:

resource "azurerm_firewall_policy_rule_collection_group" "rule_collection_group" {
  name               = "MyFirewallPolicyRuleCollectionGroup"
  firewall_policy_id = azurerm_firewall_policy.firewall_policy.id
  priority           = 100

  network_rule_collection {
    name     = "MyNetworkRuleCollection1"
    priority = 100
    action   = "Allow"
    rule {
      name                  = "WebRule"
      protocols             = ["TCP"]
      source_addresses      = ["10.0.0.1"]
      destination_addresses = ["192.168.1.1", "192.168.1.2"]
      destination_ports     = ["80"]
    }
  }
}

With this code, we will create one Firewall Policy Rule Collection Group named MyFirewallPolicyRuleCollectionGroup with one Rule Collection named MyNetworkRuleCollection1 with one Rule named WebRule.
All the details about this Terraform resource is available on the Terraform documentation.

In this example in addition of http if we want to add https we just have to modify the code like this :

resource "azurerm_firewall_policy_rule_collection_group" "rule_collection_group" {
  name               = "MyFirewallPolicyRuleCollectionGroup"
  firewall_policy_id = azurerm_firewall_policy.firewall_policy.id
  priority           = 100

  network_rule_collection {
    name     = "MyNetworkRuleCollection1"
    priority = 100
    action   = "Allow"
    rule {
      name                  = "WebRule"
      protocols             = ["TCP"]
      source_addresses      = ["10.0.0.1"]
      destination_addresses = ["192.168.1.1", "192.168.1.2"]
      destination_ports     = ["80", "443"]
    }
  }
}

Deploying this new configuration is easy as terraform apply. After few seconds, the new rule will be in place in Azure Firewall.

How to deploy automatically my code with GitLab CI/CD ?

To use GitLab CI/CD, we need:

  • Application code hosted in a Git repository.
  • A file called .gitlab-ci.yml at root level of your repository.

In the file, you can define:

  • The scripts you want to run.
  • Dependencies.
  • If the script have to run automatically or trigger manually.

The scripts are grouped into jobs, and jobs run as part of a pipeline. The CI/CD configuration needs at least one job. The jobs are organize in a sequence that suits your application and is in accordance with the tests you want to perform.

When .gitlab-ci.yml file is present in your repository, GitLab detects it and an application called GitLab Runner runs the scripts defined in the jobs.

In our case, we will define 3 stages for the 3 different Terraform commands :

  • Validate : to be sure there is not Terraform syntax error in the code
  • Plan : to generate Terraform plan file
  • Apply : to execute the plan file generate on the previous stage. We want this stage to be executed only on master/main branch and ideally after review.

The .gitlab-ci.yml file will look like this :

image:
  name: hashicorp/terraform:latest
  entrypoint:
    - '/usr/bin/env'
    - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'

before_script:
  - rm -rf .terraform
  - terraform --version
  - terraform init
 
stages:
  - validate
  - plan
  - apply

validate:
  stage: validate
  script:
    - terraform validate

plan:
  stage: plan
  script:
    - terraform plan -out "planfile"
  dependencies:
    - validate
  artifacts:
    paths:
      - planfile

apply:
  stage: apply
  script:
    - terraform apply -input=false "planfile"
  only:
    refs:
      - main
  dependencies:
    - plan

How to use a Service Principal in Terraform via GitLab?

To generate your Service Principal you can have a look on Terraform documentation.

Once you have your credentials for this Service Principal, you will have to create 4 CI/CD Variables in GitLab :

  • ARM_CLIENT_ID: with the value of your Application (client) ID
  • ARM_CLIENT_SECRET: with the client secret of your Application
  • ARM_SUBSCRIPTION_ID: with the default subscription ID
  • ARM_TENANT_ID: with your Tenant ID

All those variables have to be accessible to all branches (not protected) and I prefer to mask at least ARM_CLIENT_SECRET variable.

Firewall as Code is ready !

Normally if you have setup everything you must be now able to do Firewall as Code.

Let's do a test ! You can find some code example on this GitLab repo: https://gitlab.com/VincentMisson/firewall-as-code

git clone git@gitlab.com:VincentMisson/firewall-as-code.git
cd firewall-as-code
git checkout -b FirewallChange-CHG000001

**Update the firewall-rules.tf file with your new rules**

git commit -am "FirewallChange : CHG000001 - New rules"
git push origin FirewallChange-CHG000001

On GitLab, you must be able to see one new pipeline with 2 jobs : validate & plan.

Next, you will have to create a new merge request based on your new branch. Once your request is merged to main it will create a new pipeline with 3 jobs : validate, plan & apply.

Once the pipeline will be completed if you check on Azure portail, you must see the firewall policy and the firewall rules up to date.

As soon as you start to use IaC, it's not a good idea to do any manual change or it will be removed next time you will run the CI/CD.
If you want to be able to manage some rules using Azure portal, create a dedicate Rule Collections group not managed by Terraform.

As you can see, managing Firewall as Code can be easy and is really powerfull. But remember great power implies great responsibility so if it's not done correctly it can have a big impact (as often with network components).

Don't hesitate to contact me if you have any question or remark.