Write Custom Policies

Write Custom Policies

Create your own policies for cnspec and Mondoo to scan your infrastructure

Policies are the specifications that cnspec uses when it scans an asset. cnspec compares the asset's configuration against the standards set in a policy and calculates a score based on the comparison.

Mondoo provides dozens of free policy bundles (collections of policies) with cnspec that cover the most common asset types, and Mondoo Platform has even more. If your organization has unique needs that these policy bundles don't meet, you can create your own.

A very simple policy bundle

All cnspec policies are stored in YAML files called bundles because they bundle policies together. A policy bundle's filename ends in .mql.yaml. To learn more about policies and policy bundles, read About Policies.

Here's a very simple example of a policy bundle. It contains only one policy, Simple example policy 1:

policies:
  - uid: simple-example1
    name: Simple example policy 1
    version: '1.0.0'
    scoring_system: highest impact
    authors:
      - name: Lunalectric
        email: security@lunalectric.com
    docs:
      desc: |-
        Descriptive documentation about this policy
    groups:
      - title: group1
        filters: asset.family.contains("unix")
        checks:
          - uid: sshd-01
            title: Ensure the port is set to 22
            mql: sshd.config.params["Port"] == 22
            impact: 30

          - uid: sshd-02
            title: Prevent weaker CBC ciphers from being used
            mql: sshd.config.ciphers.none( /cbc/ )
            impact: 60

        queries:
          - uid: sshd-d-1
            title: Gather SSH config params
            mql: sshd.config.params

We'll use this simple policy bundle example to explore how to write a policy.

Basic policy attributes

The attribute...On line...Defines...
uid2A unique identifier for the policy
name3A descriptive name for the policy
version4The current version of the policy.
We recommend using semantic versioning to keep track of major and minor policy changes.
scoring_system5How Mondoo calculates the score for an asset: average, weighted, banded, decayed, or highest impact. To learn more, read Score Policies.
authors6-8The person or entity to credit for writing the policy, and email where they can be reached.
docs9-11Optional documentation section for describing the policy's purpose and makeup.

The groups section of the policy defines the checks and queries that define how to assess and report on asset security. To learn more, read Break up a Policy into Groups / Chapters.

The filters line on the group (line 14) tells cnspec which assets the group's checks and queries apply to, in this case any Unix-like system. cnspec runs a policy only on assets that match at least one of the policy's filters. If the policy has no filters, or none of them match, the scan reports asset doesn't support any policies instead of results. To learn more, read Limit Target Assets with Filters.

Queries

A query is an MQL inquiry that requests information about an asset. For example, a query can ask what version of an OS is running on a container or request the UIDs, names, and statuses of all users in an application.

Queries in a policy add helpful insights to scan report output. (They're also the bases for checks, which are described below.)

The simple example policy bundle above contains one query (on lines 27-29). It requests the configuration values of the SSH server scanned. This information is included in the scan report output.

The attribute...On line...Defines...
uid27A unique identifier for the query
title28A descriptive name for the query
mql29The MQL query that requests information, such as the number of root accounts or the state of a port

To learn how to create MQL queries, read Write Effective MQL.

Checks

An MQL query that also makes an assertion and produces a score is called a check. Checks retrieve a value just like any query. For example, a check can ask What OS version is running? How they differ from other queries is that they compare the retrieved value to a desired value and create a score based on that comparison. For example, a check can assert that the value should be 8.3.1 or higher. All checks return a Boolean true or false. In our example, if the current OS version on the scanned asset is 8.2, the check returns false. If the current OS version is 8.3.1 or 8.3.5, the check returns true.

Checks are the building blocks of policies. A typical policy identifies a number of desired configurations (such as MFA is enabled and no ports are publicly accessible) and instructs Mondoo to compare that to the actual configuration on the scan target. This is a collection of checks.

The simple example policy bundle above contains two checks:

  • The check defined in lines 16-19 ensures the SSH port is set to 22.

  • The check defined in lines 21-24 ensures that SSH uses a strong cipher.

Each check has its own attributes:

The attribute...On lines...Defines...
uid16 & 21A unique identifier for the check
title17 & 22A descriptive name for the check that's useful in report output
mql18 & 23The MQL assertion that identifies the desired condition or configuration, such as logging is enabled or encryption is required
impact19 & 24How important (on a scale from 0 to 100) the check is in the scope of the entire policy. The impact and result of each check determine the asset's score on the policy. To learn more, read Score Policies.

To learn how to create MQL queries and checks, read Write Effective MQL.

Document checks

A check can carry documentation that Mondoo Platform shows alongside its results. Add a docs section with a description (desc), instructions to verify the result by hand (audit), and one or more remediation entries. Each remediation entry has an id that names the method, such as cli or bash, and a Markdown desc. Use refs to link to external sources:

queries:
  - uid: sshd-max-auth-tries
    title: Limit SSH authentication attempts
    impact: 60
    mql: sshd.config.params["MaxAuthTries"] <= 4
    docs:
      desc: |
        Limiting authentication attempts slows down brute-force attacks.
      audit: |
        Run `sshd -T | grep maxauthtries` and verify that the value is 4 or less.
      remediation:
        - id: cli
          desc: |
            Set `MaxAuthTries 4` in `/etc/ssh/sshd_config`, then reload the SSH server:

            ```bash
            systemctl reload sshd
            ```
    refs:
      - url: https://man.openbsd.org/sshd_config
        title: sshd_config manual

To print the remediation of a check, or of every check in a policy, from bundle files on disk, run cnspec policy graph remediation. Add --id cli --code to print only the code blocks of the cli entries.

Strict mode

By default, MQL is forgiving about missing data. If a check looks up a key that doesn't exist, such as a misspelled SSH setting, the lookup returns null, and the comparison quietly returns false. A check that couldn't read what it asked about looks the same as a check that found a bad value.

Strict mode, available starting with cnspec 14, closes that gap. In strict mode, every link in an MQL chain must resolve. A lookup of a map or dict key that isn't there is an error at that lookup, so the check reports an error instead of a result. Reading a declared field that's null is still fine, but reading through a null value is an error.

When a key is legitimately optional, put ? after the link to waive it:

// Strict: errors, because the PermitRootLogn key doesn't exist
sshd.config.params.PermitRootLogn == "no"

// Strict: allowed, because ? says the key may be absent
sshd.config.params.PermitRootLogn? == "no"

To turn on strict mode for a policy, set strict: true on the policy. It applies to every query in the policy:

policies:
  - uid: simple-example1
    name: Simple example policy 1
    version: '1.0.0'
    strict: true

A policy that doesn't set strict uses the default of whoever runs it. Pass --strict to cnspec scan, or set the strict key in the cnspec configuration file or the MONDOO_STRICT environment variable, to make strict mode the default for those policies. A policy that sets strict: false always runs in non-strict mode, even with --strict. Query packs accept the same strict field.

To find policies that don't declare strict yet, lint them with the --require-strict-declaration flag. To learn more, read the cnspec policy lint CLI reference.

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.

Create and test your first policy locally

You don't need a Mondoo Platform account to write and run a policy. Everything here works with the open source cnspec CLI.

  1. Save the bundle. Copy the example above into a file named first-policy.mql.yaml.

  2. Lint it. Confirm the bundle compiles and every MQL query parses:

    cnspec policy lint first-policy.mql.yaml

    For this example, lint also prints warnings about the missing require statement and mondoo.com/category and mondoo.com/platform tags. Warnings don't stop the bundle from running; lint ends with valid policy bundle(s) as long as there are no errors.

  3. Scan an asset with it. Point cnspec at any target and pass your bundle with --policy-bundle (short alias -f). To scan the machine you're on:

    cnspec scan local --policy-bundle first-policy.mql.yaml

    cnspec runs your checks and prints a report showing which passed, which failed, and the asset's score. Add --incognito to keep the results entirely local even when you're logged in to Mondoo Platform.

  4. Iterate. Edit a check, save, and scan again. Because results depend on the asset, use cnspec shell to prototype MQL against your target and find the checks that matter for it.

Build a check for anything: open cnspec shell TARGET, prototype an MQL expression against any resource, and paste the working expression into your bundle as a check. If it returns true or false in the shell, it can be a check.

Next steps

On this page