Skip to main content

Announcing Business-aware Policies for Terraform Cloud and Enterprise

Today we’re announcing the ability for policy as code workflows, within Terraform Cloud and Terraform Enterprise, to retrieve data from multiple business systems via what we’ve called Business-aware Policies. Now organizations can query information from other systems to determine if changes in infrastructure are compliant with company policy, approving those that are compliant, and automatically rejecting or escalating changes that are in breach of policy.

The Sentinel policy as code functionality built-in to HashiCorp Terraform has been extended to allow policies to retrieve data from external APIs. Using HTTP/S, a policy can now make decisions based on the information obtained from any internal or external service to a business—anything from verifying that other departments have approved the change in ServiceNow to checking the current weather forecast. This new feature opens up a huge array of new possibilities for customers using Sentinel; we’ll explore just a few of them in this blog.

»Using the http import within a policy

The new functionality comes via the introduction of a new import, named http. We can see a minimal demonstration using the http import within the following policy:

import "http"
resp = http.get("https://example.hashicorp.com")

With the inclusion of two lines we’ve used this new addition to query an external service. The first line (import “http”) adds the new capability into the policy, while the second retrieves data from a URL (in this case “https://example.hashicorp.com”). The final step is to define a rule that makes use of that data:

import "http"
resp = http.get("https://example.hashicorp.com")

main = rule { resp.body contains "something" }

Here we are checking if the word “something” appears anywhere in the response. If it does this policy will pass, if not a failure will be returned and the run will be halted.

»Confirming deployment windows

Many of our customers have either pre-defined maintenance windows where deployments are allowed for certain systems. Or they may have the opposite where there is a blackout period to ensure stability because of an upcoming major event. In these situations there is a central source of truth about these deployment windows that is maintained by the appropriate teams. Enforcing these deployment windows was left down to the best intentions of the teams and appropriate oversight throughout the deployment process.

Now teams can write policies to automate compliance and have confidence that even well-intentioned teammates won’t accidentally let a change go through.

import "http"
import "json"

req = http.request("https://deploystatus.internal")
resp = json.unmarshal(http.get(req).body)
main = rule { resp["deploy_window_open"] is true }

In this example we’ve expanded on the previous one by also incorporating the json import. The assumption here is that the HTTP endpoint we’re querying is returning a JSON document, so we’re then converting the response body into a native Sentinel data structure. Now that we have data converted we can evaluate against it within a rule, and we check that the value of the deploy_window_open is set equal to true.

Now our policy authors can write policy that are aware of any deployment windows without having to update the policy whenever there is a change. The teams responsible for defining those windows can do so in a central location, and teams deploying changes have the extra confidence that comes with automated policy checks and don’t need to worry if they missed an email that said there was currently a deployment blackout.

For each policy, an organization can also use Sentinel enforcement levels to decide whether a failure should be escalated for manual review and approval, or automatically rejected.

»Restricting allowed sets

Another often requested use case is retrieving a set of approved values from a centralized location. Examples of this include specific network CIDR ranges for subnets (to avoid conflicts with internal ranges), or having a whitelist of pre-approved images that can be used on cloud instances. The existing approach has been to embed this information directly into the policies. While it works, this approach of hard-coding becomes a maintenance burden whenever the definition of what’s allowed changes. In addition it means the information becomes proliferated across multiple systems and there is no longer a single source of truth.

Instead of hard-coding those values, the http import can now be used to retrieve the current list of allowed values from an API:

import "http"
import "json"
import "sockaddr"
import "tfplan"

req = http.request("https://cidrrangers.internal")
resp = json.unmarshal(http.get(req).body)
disallowed_cidr_blocks = resp["internal_cidr_blocks"]

no_dissallowed_cidrs = rule {
  all tfplan.resources.aws_subnet as _, subnets {
    all subnets as _, subnet {
      all disallowed_cidr_blocks as block {
        not sockaddr.is_contained(block, subnet.applied.cidr_block)
      }
    }
  }
}

main = rule { 
  no_dissallowed_cidrs
}

This policy will now fetch a list of internal CIDR blocks from an internal API, use the tfplan import to iterate any changes to subnets that exist in the proposed plan, and then finally use the sockaddr import to ensure the configured range is outside all of the internal blocks.

»Authenticating requests

If a Terraform Enterprise install is only communicating with services within a trusted environment network level isolation may be enough to ensure data is only being requested from trusted clients.

For a lot of use cases that level of trust can’t, or shouldn’t, be assumed. So we’ve included functionality to customize the default request settings to allow requests to be authenticated:

import "http"

param token

req = http.request("https://example.hashicorp.com").
        .with_header("Authorization", "token "+token)
resp = http.get(req)

There’s a few new concepts in this example. First, we’re using the newly introduced param functionality which means we don’t need to hard-code sensitive values like usernames and passwords into our policies. Instead we can pass them in later (more on how we actually do that later too). We’ve also had to update the way we setup our request so that it can be configured to use something other than the defaults. We’ve done that by using http.request to initialize our request. We then call the with_header method to set a custom value, our API token, for the “Authorization” header. Finally we pass that request into http.get and can proceed with handling the request just as we did previously.

We’ve also included a method to make authenticating against any API that is using HTTP Basic Authentication easier to integrate with:

import "http"

param example_username
param example_password

req = http.request("https://example.hashicorp.com").
        with_basic_auth(example_username, example_password)
resp = http.get(req)

The approach is similar to the header customization, except in this scenario we can call with_basic_auth and provide our username and password which will set the appropriate header values for us.

»Configuring parameters

The previous example highlighted another new feature, the ability for Sentinel policies to receive parameters for use within the policy. You can set these values within the new Sentinel Parameters section of the Policy Sets settings page:

»Cosmic awareness

When building out this feature the team was looking for novel ways to extend policies to take advantage of the new functionality. The most creative example so far is querying the NASA API to retrieve a list of Near Earth Objects. The policy below will retrieve a list of Near Earth Objects, look for any that are potentially hazardous asteroids, and if there’s one on its way, block any changes… we assume we’ve probably got bigger things to worry about than pushing a change to production in that instance.

# no-hazardous-asteroids-today.sentinel enforces that no
# "potentially hazardous" asteroids are approaching their
# closest point to Earth within 1,000,000 miles today. Because if
# there's a potentially hazardous asteroid reaching within a
# million miles o' here, we're way too nervous and distracted to
# be changing our infrastructure right now!
import "http"
import "json"
import "strings"
import "time"

param api_token

year = string(time.now.year)
month = string(time.now.month)
day = string(time.now.day)
today = strings.join([year, month, day], "-")
base_url = "https://api.nasa.gov/neo/rest/v1/feed?"
start_query = "start_date=" + today
api_query = "&api_key=" + api_token
full_url = base_url + start_query + api_query

no_close_hazardous_asteroids = func(asteroids) {
  is_safe = true
  for asteroids as asteroid {
    hazardous = asteroid["is_potentially_hazardous_asteroid"]
    approach_data = asteroid["close_approach_data"][0]
    distance = approach_data["miss_distance"]["miles"]
    if hazardous is true and float(distance) < 1000000 {
      diameter = asteroid["estimated_diameter"]["feet"]
      max_diameter = diameter["estimated_diameter_max"]
      mph = approach_data["relative_velocity"]["miles_per_hour"]
       
      warning_message = [
        "\n😱😱😱\n",
        "The asteroid '" + string(asteroid["name"]) + "'",
        "is estimated to be up to " + string(max_diameter),
        "feet in diameter!\n",
        "And it's traveling " + string(mph) + " miles per hour!!!!\n",
        "AHHHHHHH!!!!!!\n"
      ]

      print(strings.join(warning_message, " ")
      is_safe = false
    }
  }
  return is_safe
}

resp = http.get(full_url)
near_earth_objects = json.unmarshal(resp.body)["near_earth_objects"][today]

main = rule {
  no_close_hazardous_asteroids(near_earth_objects)
}

To use the code above you’ll need to supply a NASA API key as a parameter to the policy.

»Getting started

These are just a few examples of some of the new ways customers can retrieve data from almost any business system to support their compliance decisions, while their teams can continue to confidently self-service their needs in a dynamic environment. Business-aware Policies is available to all Terraform Cloud and Enterprise customers who are already utilizing policy as code via Sentinel.

To learn more about Terraform visit https://www.hashicorp.com/products/terraform. To get started with Sentinel visit the Sentinel Learn track to learn more.


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.