Write Custom Policies

Make Policies Flexible with Variants

Use variants to change what a policy checks based on different conditions

Variants are checks that behave differently based on conditions you define. They're alternative versions of checks.

For example, suppose you want to ensure that Remote Desktop Protocol (RDP) is restricted from the internet. You want to perform this check both in GCP projects and in Terraform files. You can do this by creating one variant for GCP projects and another for Terraform files:

  • The GCP variant queries if the asset is a GCP project and checks RDP access using the GCP resource.

  • The Terraform variant queries if the asset is a Terraform file and checks RDP access using the Terraform resource.

  • If the asset is neither a GCP project nor a Terraform file, cnspec doesn't execute an RDP check.

Filters

cnspec relies on filters to determine which variant to run against an asset. A filter is a condition written in MQL. Any fields you can query about any resources can be the basis for a filter.

To learn more about filters, read Limit Target Assets with Filters.

Create variants

A bundle has two pieces:

  • The policy's groups reference the parent check by UID.
  • The top-level queries section holds the parent check (with its variants list) and each variant's definition (with its filter and MQL).
policies:
  - uid: okta-security-example-with-variants
    name: Example of a policy that uses variants
    version: '1.0.0'
    scoring_system: highest impact
    authors:
      - name: Lunalectric
        email: security@lunalectric.com
    groups:
      - title: Password requirements
        checks:
          - uid: password-minimum-length
queries:
  - uid: password-minimum-length
    title: Minimum password length
    impact: 30
    variants:
      - uid: password-minimum-length-runtime
      - uid: password-minimum-length-terraform-hcl
      - uid: password-minimum-length-terraform-plan
      - uid: password-minimum-length-terraform-state
  - uid: password-minimum-length-runtime
    filters: asset.platform == "okta-org"
    mql: |
      okta.policies.password.all( settings['password']['complexity']['minLength'] >= 15 )
  - uid: password-minimum-length-terraform-hcl
    filters: asset.platform == "terraform-hcl" && terraform.providers.one( nameLabel == "okta" )
    mql: |
      terraform.resources.where( nameLabel == /okta_policy_password/ ).all( arguments['password_min_length'] == /var/ || arguments['password_min_length'] >= 15 )
  - uid: password-minimum-length-terraform-plan
    filters: asset.platform == "terraform-plan" && terraform.plan.resourceChanges.contains( providerName == /okta/ )
    mql: |
      terraform.plan.resourceChanges.where( type == /okta_policy_password/ ).all( change.after['password_min_length'] >= 15 )
  - uid: password-minimum-length-terraform-state
    filters: asset.platform == "terraform-state" && terraform.state.resources.contains( type == /okta_policy_password/ )
    mql: |
      terraform.state.resources.where( type == /okta_policy_password/ ).all( values['password_min_length'] >= 15 )

The groups block on lines 9-12 includes the password-minimum-length check in the policy. Lines 17-21 list the four variants for that check:

  • Lines 22-25 define the password-minimum-length-runtime variant. The filter on line 23 tells cnspec to run this variant only when the asset is an Okta organization. Line 25 is the check to run when that filter matches.
  • Lines 26-29 define the password-minimum-length-terraform-hcl variant. The filter on line 27 tells cnspec to run this variant only on Okta Terraform HCL files. Line 29 is the check to run.
  • Lines 30-33 define the password-minimum-length-terraform-plan variant. The filter on line 31 tells cnspec to run this variant only on Okta Terraform plans. Line 33 is the check to run.
  • Lines 34-37 define the password-minimum-length-terraform-state variant. The filter on line 35 tells cnspec to run this variant only on Okta Terraform state files. Line 37 is the check to run.

The variants don't set their own title or impact. They inherit both from the parent check, and cnspec policy lint warns about a variant that defines them (rule query-variant-uses-non-default-fields).

Use one property for multiple variants

Often you use variants to ensure that different types of assets have one common property, as in the example above. All of the variants in the okta-security-example-with-variants policy check that the minimum password length is 15; they just check the value using different resources for different assets.

For efficiency and easier maintenance, you can write all four variants to use one property instead of defining 15 multiple times. Define the property in the policy's props section (lines 9-12), and reference it in each variant as props.minPass:

policies:
  - uid: okta-security-example-with-variants
    name: Example of a policy that uses variants
    version: '1.0.0'
    scoring_system: highest impact
    authors:
      - name: Lunalectric
        email: security@lunalectric.com
    props:
      - uid: minPass
        title: Minimum password length
        mql: '15'
    groups:
      - title: Password requirements
        checks:
          - uid: password-minimum-length
queries:
  - uid: password-minimum-length
    title: Minimum password length
    impact: 30
    variants:
      - uid: password-minimum-length-runtime
      - uid: password-minimum-length-terraform-hcl
      - uid: password-minimum-length-terraform-plan
      - uid: password-minimum-length-terraform-state
  - uid: password-minimum-length-runtime
    filters: asset.platform == "okta-org"
    mql: |
      okta.policies.password.all( settings['password']['complexity']['minLength'] >= props.minPass )
  - uid: password-minimum-length-terraform-hcl
    filters: asset.platform == "terraform-hcl" && terraform.providers.one( nameLabel == "okta" )
    mql: |
      terraform.resources.where( nameLabel == /okta_policy_password/ ).all( arguments['password_min_length'] == /var/ || arguments['password_min_length'] >= props.minPass )
  - uid: password-minimum-length-terraform-plan
    filters: asset.platform == "terraform-plan" && terraform.plan.resourceChanges.contains( providerName == /okta/ )
    mql: |
      terraform.plan.resourceChanges.where( type == /okta_policy_password/ ).all( change.after['password_min_length'] >= props.minPass )
  - uid: password-minimum-length-terraform-state
    filters: asset.platform == "terraform-state" && terraform.state.resources.contains( type == /okta_policy_password/ )
    mql: |
      terraform.state.resources.where( type == /okta_policy_password/ ).all( values['password_min_length'] >= props.minPass )

Tip: To check for errors in the policy bundles you write, run cnspec policy lint BUNDLE-NAME.mql.yaml. For BUNDLE-NAME, substitute the name of your file.

To learn more about properties, read Define Properties.

Next steps

On this page