Using Sentinel Policy to enforce continuous deployment windows

Sentinel, is the HashiCorp framework for policy as code management, it is built to be embedded in existing software to enable fine-grained, logic-based policy decisions. A policy describes under what circumstances specific behaviors are allowed. In our last couple of posts, we looked at the concepts behind policy as code and how you can use it with the HashiCorp enterprise products like Terraform Enterprise. In this post, we are going to examine how Sentinel Policy and the Sentinel Simulator can be used to ensure your Continuous Delivery system only deploys your application within a specified time window.

Continuous Delivery (CD) is the next level to Continuous Integration (CI), in addition to building and testing your application, you also automatically deploy built artifacts into your production environment. It is synonymous with agile and moving fast, and while you will undoubtedly have unit, integration, functional, and a whole host of other tests covering your work, defects also include misunderstood scope or missing elements from a specification. Sometimes it is not until the code hits a production environment and we see anomalies in logging or other metrics that we realize there is a problem. For this reason, many teams choose to operate a deployment window; the code is only deployed automatically during set hours when there is appropriate staffing to monitor and potentially revert a buggy release.

In the same way that we can embed Sentinel into a pipeline to enforce policy for Terraform plans, or Vault secrets, we can also enforce policy in a continuous delivery pipeline.

The examples in this post use the free to use Sentinel Simulator: sentinel v0.1.0 Binaries | HashiCorp Releases

»Writing Sentinel Policy

The first thing we need to do is to write our policy for our deployment window, this is only going to allow deployments between the hours of 9 am and 5 pm.

import "time"

timespace = time.now

// Note: Time is CET by default
is_weekday = rule { timespace.day not in ["saturday", "sunday"] }
is_deployment_hours = rule { timespace.hour > 8 and timespace.hour < 17 }

main = rule {
    is_deployment_hours and is_weekday
}

»Testing Sentinel Policy

To ensure our Sentinel policy is functioning correctly we can write tests to check the rules. Test files are stored in a top-level test folder with the tests for each policy in a sub-folder with the same name as the policy. In our example our folder structure is going to look something like the following:

$ tree 
.
├── deployment_window.sentinel
└── test
    ├── deployment_window
    │   ├── fail_deployment_hours.json
    │   ├── fail_weekday.json
    │   └── pass.json

Tests are declared using a simple JSON format, to ensure that we do not have to change the clock on our computer or wait until a particular time to test our policy, we are going to use one of the in-built features of the Simulator which allows mocking for any of the imported functions:

{
  "mock": {
    "time": {
      "now": {
        "day": "tuesday",
        "hour": 9
      }
    }
  },

  "test": {
    "is_deployment_hours": true,
    "is_weekday": true,
    "main": true
  }
}

We are mocking time.now and setting a fixed hour and day; we also set out the conditions for a successful test. In our case the rules is_deployment_hours, is_weekday, and main should all evaluate to true.

To test our policy we use the sentinel test --verbose command, the --verbose flag gives us additional output regarding the rules in the test.

$ sentinel test --verbose deployment_window.sentinel
  PASS - test/deployment_window/pass.json
    trace:
      TRUE - deployment_window.sentinel:9:1 - Rule "main"
        TRUE - deployment_window.sentinel:10:2 - is_deployment_hours
          TRUE - deployment_window.sentinel:7:30 - timespace.hour > 8 and timespace.hour < 17
            TRUE - deployment_window.sentinel:7:30 - timespace.hour > 8
            TRUE - deployment_window.sentinel:7:53 - timespace.hour < 17
        TRUE - deployment_window.sentinel:10:26 - is_weekday
          TRUE - deployment_window.sentinel:6:21 - timespace.day not in ["saturday", "sunday"]
      
      TRUE - deployment_window.sentinel:7:1 - Rule "is_deployment_hours"
        TRUE - deployment_window.sentinel:7:30 - timespace.hour > 8
        TRUE - 
				deployment_window.sentinel:7:53 - timespace.hour < 17
      
      TRUE - deployment_window.sentinel:6:1 - Rule "is_weekday"

»CI Deployment Workflow

Now we have our policy and some tests verifying its correctness, we can setup our CI job. For this example we are getting meta and have a repository containing all of our Sentinel policy, this should only be released during our allowed hours. To create the automated process for testing and deployment, we need to setup a workflow using CircleCI; the principles will be the same regardless of your CI environment.

The CircleCI configuration contains two jobs, one to test our Sentinel policy, and one which deploys it to GitHub releases. In the below snippet we can see the steps for the deploy job, the full configuration can be found at the following link: https://github.com/nicholasjackson/sentinel_aws_example/blob/master/.circleci/config.yml

version: 2.0

# ...

  deploy:
    machine:
      enabled: true
    steps:
      - checkout
      - run:
          name: install dependencies
          command: |
            wget https://releases.hashicorp.com/sentinel/0.1.0/sentinel_0.1.0_linux_amd64.zip
            unzip sentinel_0.1.0_linux_amd64.zip
            wget https://github.com/aktau/github-release/releases/download/v0.7.2/linux-amd64-github-release.tar.bz2
            tar -xvf linux-amd64-github-release.tar.bz2
      - run:
          name: check deployment window
          command: ./sentinel apply deployment_window.sentinel
      - run:
          name: release
          command: |
            ./bin/linux/amd64/github-release release \
              --user nicholasjackson \
              --repo sentinel_aws_example \
              --tag v0.1.0 \
              --name "Latest sentinel policy"

#...

To ensure that our project only deploys during our defined hours we execute our policy with the Sentinel Simulator command: sentinel apply deployment_window.sentinel. If the policy passes then the command returns a status code 0, if it fails it returns status code 1, and if the policy contains errors it returns status code 2.

      - run:
          name: check deployment window
          command: ./sentinel apply deployment_window.sentinel

Now when we push our code to CircleCI the job will first run the test job and when that passes the deploy job will run.

Because one of the conditions in our deploy step is to apply our Sentinel policy, we can now be sure that we only release inside our deployment window. When the policy passes, our job continues and creates a new release in GitHub.

If we try to deploy outside the window the deploy job fails, and nothing is released.

»Summary

This post has shown how HashiCorp's Sentinel Policy framework can be used to enforce a deployment window for your Continuous Delivery process. The code repository accompanying this post can be found here: GitHub - nicholasjackson/sentinel_aws_example: Example Sentinel policy to check AWS providers

For more information on Sentinel and how it integrates into HashiCorp enterprise products, please follow the below links:

Sign up for the latest HashiCorp news

By submitting this form, you acknowledge and agree that HashiCorp will process your personal information in accordance with the Privacy Policy.