Skip to main content

Policy as Code - Intro to Mondoo Query Language (MQL)

This page is an introduction to the Mondoo Query Language (MQL) for developing policies as code for your business-critical infrastructure. To follow along with the guides in this article you should already have your account setup and configured on Mondoo Platform, and have Mondoo Client installed and configured on a macOS, Windows, or Linux host. If not, check out our Quick Start guide in the previous section.

info

If you encounter any issues with the steps in this guide, reach out to us in the Mondoo Community Discord channel. We are here to help!

Policy as Code

Security policies, compliance frameworks, or other types of regulatory policies, typically start in the form of a document that describes the policy, the rationale for it, as well as the impact, risk, or consequence if the policy is not followed. Some of the best examples of security policies are the CIS Benchmarks which cover everything from operating systems, to containers and Kubernetes, and entire cloud platforms.

While the CIS Benchmarks provide detailed information for each individual rule or control, including auditing and remediation steps, it still falls to individuals within an organization to carry out the work of implementing these policies. The work to prove compliance with CIS Benchmarks is often manual, which can lead to errors. When carried out as an exercise such as passing an audit, manual compliance only provides a temporary, snapshot in time, rather than an automated and continuous assessment.

As change is constant in modern application and infrastructure environments, it is critical businesses have a way of applying policy in a manner that is fast, efficient, and fully automated using code.

Introducing the Mondoo Query Language (MQL)

Mondoo Query Language (MQL) is a lightweight, ultra-fast query language purpose-built for searching and filtering infrastructure configuration data, and for making assertions about infrastructure configuration data. The language itself has similarities to GraphQL for data extraction, but also shares similarities to JavaScript for scripting.

The goal of MQL is to enable Infrastructure Developers, SREs, Production Engineers, DevOps Engineers, Security Engineers ... you name it, to query business-critical infrastructure in real-time, and to be able to translate policy into code that can be run continuously across any environment from build-time, to runtime.

Translating Security Policy to MQL

To understand how security policies and controls are translated into Mondoo queries, we are going to take a look at the Mondoo Security Baseline policies, which are enabled by default in every new Space in Mondoo Platform. These polices provide a great starting examples for MQL as they are based off the CIS Benchmarks for macOS, Windows, and Linux, and will work with any workstation (macOS, Windows, Linux) system we support.

Choose the platform you have installed Mondoo Client on below, and let's dig into Mondoo Policies and how we use MQL to translate policy into code.

Locate Disable Remote Login

Let's take a look at the control titled "Disable Remote Login":

  1. Log in to your account on Mondoo Platform
  2. Navigate to POLICY HUB
  3. In the Filter policies... search box, search for "Mondoo macOS Security Baseline" then click on the policy get more details.
  4. Click on the QUERIES tab to view all of the queries in the policy.
  5. In the Filter queries... search box, search for "Disable Remote Login"

This control is straight out of the CIS Benchmark for Apple macOS which defines that Mac hosts should not allow inbound SSH connections.

Evaluating Configuration the Native Way

To assess this configuration natively via the terminal in macOS, you could use the systemsetup command. The systemsetup command requires administrative privileges to run, so we will need to pass the --sudo flag to execute it.

Get remote login configuration with systemsetup command
sudo systemsetup -getremotelogin

A system where Remote Login is disabled will return the following:

Remote Login: Off

While running this command ad-hoc shows whether or not that setting is configured according to the policy on the host, turning that into an actual security test would still require further development to write an automated test that parses the output of the command, and returns a true or false.

Additionally, the test would still need to be configured to run continuously, and to send the results where they can be monitored and acted upon should the system fall out of configuration.

Evaluating Configuration the Mondoo Way

With MQL you can both retrieve the configuration data about a host, AND make assertions about that configuration at the same time.

The following example queries the host for the configuration of the remote login setting, but also uses the == comparison operator which will evaluate as true or false.

Get remote login configuration with MQL
macos.systemsetup.remoteLogin == "Off"

A system where Remote Login is disabled will return the following:

[ok] value: "Off"

This one query is part of a larger Mondoo policy that can easily be configured to run continuously across your entire fleet of macOS hosts, and report into Mondoo Platform where alerts notify you when hosts deviate from defined policy.

That is great and all, but how does it really work???

We are so glad you are interested in how MQL really works! Ready to dive in under the hood and see what powers this engine? If so, then let's move on to the next section and take a look at Mondoo Shell.

Infrastructure Developer...meet Mondoo Shell


Mondoo Client has a number of capabilities to help you secure your assets, develop policies, and manage Mondoo Platform, but if you are an Infrastructure Developers, SREs, Production Engineers, DevOps Engineers, Security Engineers, or just someone who loves dig into infrastructure using automation, then you are really going to love Mondoo Shell!

Mondoo Shell is an interactive development environment built into Mondoo Client that is used to query assets and their configuration using MQL. The most common uses for Mondoo Shell is live querying assets, and developing policies. We are going to start with using Mondoo Shell on the local system, but in other tutorials we will dive deeper and show you how to use it with remote targets such as VMs, containers, container registries, Kubernetes clusters, and even entire cloud environments.

Select the platform you installed Mondoo Client on below, and let's check out Mondoo Shell.

info

Mondoo queries only have the permissions of the user the executes a query. In the case where you need elevated permissions to query an asset you can use sudo before executing the command mondoo scan or mondoo shell.

As we are going to do some exploring with Mondoo Shell and the query from the previous section requires administrative permissions to run, we are going to need to launch Mondoo shell using sudo.

Open a terminal and run the following command:

Launch mondoo shell on macOS with elevated permissions
sudo mondoo shell

When Mondoo Shell starts, you will be taken to a prompt where you can execute MQL queries.

Resources

Resources are the most fundamental building-block for writing queries. They provide an interface to audit the configuration of an asset. The following are a few examples of MQL resources for Servers & Endpoints that run any of the operating systems supported by Mondoo:

  • platform - Query the host for information about the platform including name, family, release, and more
  • user - Query the host for information about users including the name, UID, GID, home, shell, and more
  • file - Query files on a given host for information including owner, group, mode, contents, and more
  • packages - Query the host for information about packages on the host including name, version, installed, outdated, and more

A full list of available resources can be found by running the help command within Mondoo Shell:

Running help command in Mondoo Shell lists all available resources
help

This will return the available resources and a description of the resource. You can also run help <resource_name> to get more information on any given resource.

Let's start off by looking at the platform resource which is common to all operating systems supported by Mondoo

In the mondoo shell run the following command to get the platform name:

Run help for the platform resource
help platform

Example Output:

platform:                                 Common platform information (OS, API, Service)
release string: Release version of the platform
virtualization platform.virtualization:
title string: Human-readable name of the platform
runtimeEnv string: Contextual information about the runtime (bare-metal, cloud, container, etc)
vulnerabilityReport dict: full advisory & vulnerability report
cves platform.cves:
build string: Build version of the platform (optional)
exploits platform.exploits:
name string: Name of the platform
arch string: Architecture this OS is running on
kind string: Kind of this platform (API, code, image, package, etc)
eol platform.eol:
advisories platform.advisories:
family []string: List of platform families that this platform belongs to
...

Fields

Every resource provides Fields that are used to access specific configuration values for an asset. The output above shows us that the platform resource provides fields such as name, family, cves, and more.

For example, by running the command platform.name you can access the name of platform:

Run platform.name
platform.name
Example Output from macOS
mondoo> platform.name
platform.name: "macos"

You can also get the specific release version:

Run the platform.release
platform.release

Example Output:

Example Output from macOS
mondoo> platform.release
platform.release: "11.6""

Retrieving Multiple Fields with MQL

Similar to GraphQL, you can also filter results and return multiple fields at once. Run the following command to retrieve the values for multiple fields you type the resource {field1 field2 field3 }:

Return multiple fields for the platform resource
platform { name release arch family }

Example output:

Example Output From macOS
platform: {
arch: "arm64"
name: "macos"
family: [
0: "darwin"
1: "bsd"
2: "unix"
3: "os"
]
release: "11.6"
}

Built-In Operators & Functions

Developing policy as code relies on writing queries that answer specific questions about infrastructure:

  • Is the correct version of Python installed?
  • Is root login disabled on a host?
  • Does John Smith have an account on a host?
  • Are all EBS volumes encrypted?
  • Are my Kubernetes clusters configured to allow running privileged containers?

To facilitate this, MQL has many built-in operators and functions that can be used to filter, and transform the data from queries, allow you to make finely tuned assertions about your infrastructure. Rather than listing out all of the built-in functions and operators, we are going to look at some real-world examples of MQL queries from actual policies. In this next section, we will cover a process you can use to breakdown, and dissect any MQL query to understand how it works, and provide you the skills to continue learning the language to develop your own policies.

Choose the operating system you have installed Mondoo Client on to start exploring MQL queries, operators, and function.

To explore the usage of MQL operators and functions on macOS, we are going to continue to look at examples from the Mondoo macOS Security Baseline.

MQL Example 1: Remove Guest home folder

To begin with we are going to look at basic control titled "Remove Guest home folder". This control states that there should not be a /Users/Guest directory on the host. Let's start off by simply referencing the MQL query as it exists in the policy.

Remove Guest home folder
file('/Users/Guest').exists == false

The first thing you should notice is that the query is built off of the file resource. Running the help file command we can see there are a number of fields available including one called exists which indicates whether the file/directory exists on the host:

mondoo> help file
file: File on the system
exists bool: Indicator if this file exists on the system
size int: Size of this file on disk
basename string: Filename without path prefix of this file
content string: Contents of this file
permissions file.permissions:
user user: Ownership information about the user
group group: Ownership information about the group
path string: Location of the file on the system
dirname string: Path to the folder containing this file
...

The file resource takes a path to the file or directory as an argument, which can then be followed with the .exists function:

Mondoo Shell: file.exists
file('/Users/Guest').exists

Running this query as is will return true or false if that directory exists:

mondoo> file('/Users/Guest').exists
file.exists: false

To turn this into an assertion, we can use the == operator and specify that we expect that value to be false:

Mondoo Shell: file.exists assertion
file('/Users/Guest').exists == false

Now when we run the query we will get result from the assertion:

mondoo> file('/Users/Guest').exists == false
[ok] value: false

The example above shows a basic example of using an MQL resource (i.e. file) with a built-in function (i.e. .exists) along with the relational operator == to make an assertion.

MQL Example 2: Set an inactivity interval of 20 minutes or less for the screen saver

This control from the CIS Apple macOS 11.0 Benchmark - Level 1 states that regular users on the host should be configured to start a screen saver in 20 minutes or less if the system is idle, or unattended.

As macOS can be configured to have multiple users of any given system, this control must be implemented not just for the currently logged in user, but for all "real" users of the system. A real user would be defined as a user that log in to the system, rather than a system user, or the root account.

Let's first of all start off and look at the query as it is defined in the policy in Mondoo Platform:

Set an inactivity interval of 20 minutes or less for the screen saver
users.where( name != /^_/ && shell != "/usr/bin/false" && name != "root" ).list {
parse.plist( home + "/Library/Preferences/ByHost/com.apple.screensaver." + os.machineid.upcase + ".plist").params["idleTime"] <= 1200
}

The first thing you should notice is that the query is built off of the users resource. Running help users you would see that builds a list of user objects on the system, and that the users resource provides a .list function to access that list. Additionally, running help user shows us the various fields each user object provides.

mondoo> help users
users: Users configured on this system
list []user:
mondoo> help user
user: User on this system
uid int: User ID
gid int: User's Group ID
shell string: Default shell configured
enabled bool: Indicates if the user is enabled
sshkeys []privatekey: List of SSH keys
sid string: User's Security Identifier (Windows)
name string: Name of the user
home string: Home folder
authorizedkeys authorizedkeys: List of authorized keys
users: Users configured on this system
list []user:

Simply running users.list will produce a list of user objects from users on the host, including the system users, and the root users that are not part of the scope for this control. In order to filter the results so that they do not contain the system users, and root user, the query leverages the built-in .where function which can be used to match specific criteria within the results of a query.

In the example above, the .where function is used to look for users whose name do not begin with an "_". Let's start by looking at the results this produces by just running that part of the query:

MQL Query: Returns a list of user objects whose name does not begin with '_'
users.where( name != /^_/).list

Example Output

mondoo> users.where( name != /^_/).list
users.where.list: [
0: user id = user/-2/nobody
1: user id = user/1/daemon
2: user id = user/0/root
3: user id = user/501/jsmith
]

While the results of this may look slightly different on your system, they should produce a list of user objects that include a few system users, the root user, and your user account.

The next thing to do is to filter those results down so that they are only showing regular users. To achieve that let's first look at the available fields on the user resource again by running help user:

mondoo> help user
user: User on this system
uid int: User ID
gid int: User's Group ID
shell string: Default shell configured
enabled bool: Indicates if the user is enabled
sshkeys []privatekey: List of SSH keys
sid string: User's Security Identifier (Windows)
name string: Name of the user
home string: Home folder
authorizedkeys authorizedkeys: List of authorized keys
users: Users configured on this system
list []user:

If we take the query from above and we filter the results to return the name and the shell field we get the following:

MQL Query: Returns a list of user objects where the name does not begin with '_' and filters results to return the 'name' and 'shell' fields
users.where( name != /^_/).list { name shell }

Example Output

mondoo> users.where( name != /^_/).list { name shell }
users.where.list: [
0: {
name: "nobody"
shell: "/usr/bin/false"
}
1: {
name: "daemon"
shell: "/usr/bin/false"
}
2: {
name: "root"
shell: "/bin/sh"
}
3: {
name: "jsmith"
shell: "/bin/zsh"
}
]

The output above shows us that the system accounts have a shell configured to /usr/bin/false allowing us to filter out those accounts with shell != "/usr/bin/false". The last account we need to filter out is the root account which is done with name != "root".

When using the .where function, we can string together multiple filters using the && operator, which allows us to match on both the shell field, and the name field.

The following query produces a list of regular users on the host that are in scope for the control we are implementing:

MQL Query: Returns list of user objects for regular user accounts
users.where( name != /^_/ && shell != "/usr/bin/false" && name != "root" ).list

Example Output

mondoo> users.where( name != /^_/ && shell != "/usr/bin/false" && name != "root" ).list
users.where.list: [
0: user id = user/501/jsmith
]

Now that we understand how we can use .where along with the && operator to filter out results, we are ready to make assertions on the configuration for each regular user we matched.

The screen saver settings that we need to audit are set in a .plist file for each user at the path $HOME/Library/Preferences/ByHost/com.apple.screensaver.<machine_id>.plist. This produces a new challenge because the file path must be dynamically generated from host to host in order to make the control work on any system you run it on. The machine_id for this particular file is also expressed in uppercase letters. For example, the configuration for jsmith would look like this:

/Users/jsmith/Library/Preferences/ByHost/com.apple.screensaver.CE383555-504F-5CAE-9390-3FCF9375897C.plist

The query as it is written in the Mondoo macOS Security Baseline looks like this:

parse.plist( home + "/Library/Preferences/ByHost/com.apple.screensaver." + os.machineid.upcase + ".plist").params["idleTime"] <= 1200

Let's break this down. First off we can see it uses the parse.plist resource.

mondoo> help parse.plist
parse.plist: Parse Plist files
file file: File that is being parsed
content string: Raw content of the file that is parsed
params dict: The parsed parameters that are defined in this file

The parse.plist resource is used to parse plist files. It provides a .params field, which creates a dict object of key/value pairs for the file. Before we can use that we first need a way to dynamically generate the $HOME directory for each user, and the machine_id for each host the query is executed on. Luckily, MQL is perfect for dynamically generated configuration files such as this. Let's break it down.

Since each user object already has a field for home which provides the path to the users home directory, we can leverage that for the beginning of the file path. To test this out in Mondoo Shell we can take our same query from above and filter the results on { name home}:

MQL Query: List of user objects filtered on name and home fields
users.where( name != /^_/ && shell != "/usr/bin/false" && name != "root" ).list { name home }

Example Output

mondoo> users.where( name != /^_/ && shell != "/usr/bin/false" && name != "root" ).list { home name }
users.where.list: [
0: {
home: "/Users/jsmith"
name: "jsmith"
}
]

Now that we have a way to dynamically generate the home directory for each user, we just need to get the machine id for each host. MQL provides the os resource which provides useful information about the operating system Mondoo is running on. Run help os to view the fields available:

mondoo> help os
os: Operating System information
env map[string]string: ENV variable contents
path []string: PATH variable contents
uptime time: Current uptime
rebootpending bool: Indicates if a reboot is pending
name string: Pretty Hostname on Linux / Device name on Windows
updates []os.update: List of available OS updates
hostname string: Hostname for this OS
machineid string: Machine ID for this OS
update os.update:
rootcertificates os.rootcertificates:
os.rootcertificates: Operating System root certificates
files []file: List of files that define these certificates
content []string:
list []certificate:
os.update: Operating System update information
name string: Name of the update
category string: Category of the update
severity string: Severity of the update
restart bool: Indicates if a restart is required
format string: Package format for this update

As you can see from the output above, the os resource provides the machineid field which provides the Machine ID for the operating system.

mondoo> os.machineid
os.machineid: "ce383777-504f-5cae-9390-3fcf9375897c"

This produces the machineid, but we also need that ID to be in uppercase letters. MQL has the built-in .upcase function which we can leverage as well:

MQL Query: os.machineid.upcase
os.machineid.upcase

Example Output

mondoo> os.machineid.upcase
os.machineid: "CE383555-504F-5CAE-9390-3FCF9375897C"

Now we have a full path to the plist file. To look at the .params for that file for the user jsmith and the example machineid we could run the following command:

mondoo> parse.plist('/Users/jsmith/Library/Preferences/ByHost/com.apple.screensaver.CE383555-504F-5CAE-9390-3FCF9375897C.plist').params
parse.plist.params: {
CleanExit: "YES"
PrefsVersion: 100.000000
idleTime: 600.000000
moduleDict: {
moduleName: "iLifeSlideshows"
path: "/System/Library/Frameworks/ScreenSaver.framework/PlugIns/iLifeSlideshows.appex"
type: 0.000000
}
tokenRemovalAction: 0.000000
}

Now we have all of the available params for that file, we need to access the idleTime param, which can be done by adding ['param name'] to the end of the query as follows:

mondoo> parse.plist('/Users/jsmith/Library/Preferences/ByHost/com.apple.screensaver.CE383555-504F-5CAE-9390-3FCF9375897C.plist').params['idleTime']
parse.plist.params[idleTime]: 600.000000

Now the only left to do is to use the <= operator to ensure the value that is set to is less than 1200:

mondoo> parse.plist('/Users/jsmith/Library/Preferences/ByHost/com.apple.screensaver.CE383777-504F-5CAE-9390-3FCF9375694C.plist').params ['idleTime'] <= 1200
[ok] value: 600.000000

This one query covers a number of new concepts:

  • .where function - Build a list of user objects using users.where to filter users that match multiple filters with the && operator.
  • + operator - Using the + to concatenate strings: user.home + /path/to/file. + os.machine.upcase + .plist
  • .upcase function - Transform a string to uppercase with the .upcase function
  • parse.plist - Use the parse.plist resource to access the key/value pairs in a .plist file, and access specific key/values by with .params[<key_name>]

Hopefully the process of dissecting the example queries above has given you a better idea of how MQL queries are constructed. The Mondoo Policies in Mondoo Platform are a great resource for learning the language. If you follow the steps above to break down the queries you will soon be developing MQL queries for your own environment, and hopefully sharing them back with the rest of the Mondoo Community!

Remote Targets with Mondoo Shell

In addition to running against the localhost, Mondoo Shell provides transport capability using the --connection or -t flag, which allows you to connect to remote targets, where you can execute MQL queries on assets in real-time.

The following are examples of using Mondoo Shell against remote targets:

Mondoo Shell leverages your local AWS credentials ~/.aws/credentials to make API calls on your behalf. If you have multiple AWS accounts configured in your credentials file you can use the AWS_PROFILE environment variable to set which profile to use.

Connect to AWS

To start a Mondoo Shell targeting AWS run the following command:

mondoo shell -t aws://

AWS Resources

To view all of the available MQL resources for AWS run help aws in Mondoo Shell:

List available MQL resources for AWS
help aws

You can get more information about any specific resource by running help <resource_name> for that resource:

mondoo> help aws.ec2.instance
aws.ec2.instance: AWS EC2 Instance
patchState dict: patch state information about the instance
instanceStatus dict: the status of the specified instance
tags map[string]string:
state string: state of the instance
stateTransitionReason string: reason for the most recent state transition
ebsOptimized bool: denotes whether or not instance has ebs optimization turned on
device aws.ec2.instance.device:
httpTokens string: a value of optional for http tokens denotes imdsv1 server compatibility; required is imdsv2
deviceMappings []aws.ec2.instance.device: a list of devices attached to the instance (e.g. ebs volume)
securityGroups []aws.ec2.securitygroup: a list of security groups (ids) associated with the instance
publicDnsName string: public dns name for the instance
arn string: arn for the instance
instanceId string: instance id for the instance
publicIp string: public ip for instance
vpc aws.vpc: vpc associated with the intance
stateReason dict: reason for the most recent state transition
detailedMonitoring string: indicates whether detailed monitoring is enabled
region string: region where the instance exists
ssm dict: Amazon Systems Manager information for the instance
instanceType string: instance type, e.g. t1.micro

Example Queries

The following are some example queries from the CIS Amazon Web Services Foundations Benchmark:

MQL query to ensure IAM password policy requires minimum length of 14 or greater
aws.iam.accountPasswordPolicy['MinimumPasswordLength'] >= 14
MQL query to ensure EBS encryption is enabled by default in all regions
aws.ec2.ebsEncryptionByDefault.values.all(_ == true)
MQL query to ensure that all the expired SSL/TLS certificates stored in AWS IAM are removed
aws.iam.serverCertificates.length == 0 || aws.iam.serverCertificates.all(parse.date(_['Expiration'], 'RFC3339') > time.now)
MQL query to ensure that all the expired SSL/TLS certificates stored in AWS IAM are removed
aws.s3.buckets.all(encryption['Rules'][0]['ApplyServerSideEncryptionByDefault']['SSEAlgorithm'] == "AES256" ||
encryption['Rules'][0]['ApplyServerSideEncryptionByDefault']['SSEAlgorithm'] =="aws:kms"
)

Exit the Shell

When you are done playing with Mondoo shell you can simply type exit to exit out of the shell.

Check out the mondoo shell --help command for additional information on using Mondoo Shell.

Next Steps


Hopefully this has given you a sense of the power of Mondoo and MQL. If you are interested in learning about what else you can do with MQL check out the MQL Resources.

Another great place to learn more about the power MQL is to look at the queries in the policies in Mondoo Platform. The queries provide endless examples of how Mondoo Engineers use MQL to write policies.

Stay tuned for more documentation on using MQL coming soon!