Enable/disable Sentinel Policies Based on Environment

Is it possible for us to enable or disable Sentinel Policies based on a environment variable or a flag in Terraform Workspaces?

As a developer, I want to disable or ignore some network rules in development environment for debugging, but apply strict Deny All network rules for some cloud resources in production environment.

For example,

policy "azure-cis-3.7-storage-default-network-access-rule-set-to-deny" {
  source = "https://raw.githubusercontent.com/hashicorp/terraform-foundational-policies-library/master/cis/azure/storage/azure-cis-3.7-storage-default-network-access-rule-set-to-deny/azure-cis-3.7-storage-default-network-access-rule-set-to-deny.sentinel"
  enforcement_level = "hard-mandatory"
  enforcement_environment = "production,test"
}

policy "azure-cis-3.7-storage-default-network-access-rule-set-to-deny" {
  source = "https://raw.githubusercontent.com/hashicorp/terraform-foundational-policies-library/master/cis/azure/storage/azure-cis-3.7-storage-default-network-access-rule-set-to-deny/azure-cis-3.7-storage-default-network-access-rule-set-to-deny.sentinel"
  enforcement_level = "ignored"
  enforcement_environment = "development"
}

I understand that we can manage Sentinel Policies based on a different Policy Sets, but it becomes difficult to apply where each Terraform GitHub project has three or more Environment/Workspace.

Hi @leonard,

It is not possible to set flags on workspaces to make Sentinel policies selectively apply to them. But you can accomplish what you want based on the names of the workspaces.

I recommend using the tfrun import to check the name or other properties of the workspace and effectively only apply the policies when the name matches certain patterns. You can see an example of this in this limit-cost-by-workspace-name.sentinel policy which uses this common function. Note that you could enforce workspace naming conventions by writing your policy in a way that fails if the workspace name does not match one of several specific patterns.

Roger Berlind
Global Technology Specialist

1 Like

@leonard another method which I use all the time in the foundational policies, is the when predicate in a rule expression.

import "tfrun"
import "strings"

// Change this to prod to trigger production rule evaluation
environment = "dev"

prod_eval = rule when environment is "prod" {
	print("Production rule evaluated")
}

dev_eval = rule when environment is "dev" {
	print("Development rule evaluated")
}

main = rule {
	prod_eval and dev_eval
}

You can easily change this policy to use the value in tfrun.workspace.name to evaluate which environment is being provisioned by Terraform:

import "tfrun"
import "strings"

// Change this to prod to trigger production rule evaluation
environment = strings.split(tfrun.workspace.name, "-")[0]

prod_eval = rule when environment is "prod" {
	print("Production rule evaluated")
}

dev_eval = rule when environment is "dev" {
	print("Development rule evaluated")
}

main = rule {
	prod_eval and dev_eval
}

You can experiment with the above in the Sentinel Playground. Hope this helps :slight_smile:

1 Like

@hcrhall I checked this thread and added a filter to check based on workspace name. However, the team is recommending using the filter for checking the “prod” keyword in the subscription name. Can you suggest if that’s possible as I can’t find the subscription name in the tf state file.

“Is is possible to grab the subscription name from the resource group Azure ID and apply regex which matches prod”

import “tfplan/v2” as tfplan
import “tfrun”
import “strings”

#Only apply this policy if its azurerm provider
provider = tfrun.providers contains “azurerm”

Validate the environment based on workspace and apply this policy

environment = strings.split(tfrun.workspace.name, “-”)

allVnet = filter tfplan.resource_changes as _, resource_changes {
resource_changes.type is “azurerm_virtual_network” and
resource_changes.mode is “managed” and
(resource_changes.change.actions contains “create” or
resource_changes.change.actions is [“update”])
}

allVnetDdosPlan = filter tfplan.resource_changes as _, resource_changes {
resource_changes.type is “azurerm_network_ddos_protection_plan” and
resource_changes.mode is “managed” and
(resource_changes.change.actions contains “create” or
resource_changes.change.actions is [“update”])
}

print(“Ensure that ddos protection plan is enabled for Vnet”)

allow_only_vnets_with_ddos_protection_plan = rule when (environment contains “prod” and provider is true){
all allVnet as _, ddos_protection {
all ddos_protection.change.after.ddos_protection_plan as ddos_protection_plan {
ddos_protection_plan.enable is true
}
}
}.

Also, if you can help in writing a post on integrating sentinel in CI/CD without using terraform.io. Our team is moving to use Jenkins with Spinnaker.

Hi @jhabikal21,

When you say subscription are you referring to an Azure subscription?

Regarding your query about Sentinel in CI/CD. The Sentinel integration is executed in https://app.terraform.io. You could try and use some script magic to wrap the Sentinel CLI in your pipeline, but it’s a fair bit of effort to get this to working properly, and the results may be somewhat unpredictable.

May I inquire as to why you would like to use CI/CD instead of the Terraform Platform? Do you have a workflow issue that is blocking your progress or is it something else entirely?

Yes, I mean Azure subscription. In our org, the team tends to add prod keywords in the Azure subscription name. Can we filter with this to identify prod env and apply necessary policy.

I don’t believe this is possible using state because state only shows the unique identifier for the Azure subscription.

One possible way of doing this would be to use an alias on the provider block:

provider "azurerm" {
  alias = "production"
  features {}
}

You could then use the tfconfig import to get the alias value which may allow you to determine which environment you are provisioning resources in:

import "tfplan/v2" as tfplan
import "tfconfig/v2" as tfconfig

environment = values(tfconfig.providers)[0].alias

main = rule when environment is "production" {
   all tfplan.resource_changes as _, rc {
			rc.change.after.location is "australiaeast"
   }
}

This is a hideous example of how to get the value of the alias. I recommend creating a module that contains a function that would return only the data you are after and supports multiple providers etc. If you need some inspiration, take a look at the following:

Can you show me a way wherein I can check if the passed Azure subscription ID is from the inclusion list, only then apply the rule? Is that possible? @hcrhall

It’s possible, but it requires a fair bit of boilerplate to get the job done. Here is an example where I use a module to define a list of production subscription ids.

https://play.sentinelproject.io/p/UdAQb6Rvkl7