Custom Rules

Note

Fugue’s custom rules feature allows you to add user-defined compliance rules to your environment. Fugue evaluates your infrastructure against the rules and displays compliance results as it would for out-of-the-box standards such as SOC 2 or HIPAA. The visualizer also denotes noncompliance with custom rules.

A custom rule is a type of compliance control used to assess adherence to a user-defined resource configuration.

You can use custom rules to enforce enterprise policies.

Custom rules function at the organization level, so they are automatically enabled for all your environments.

What are Custom Rules?

Custom rules are written using Rego, an open source policy-as-code language for Open Policy Agent (OPA).

Rego is a query language. A Rego rule consists of one or more queries that return information from a Fugue scan. Fugue uses Rego rules to determine whether a resource is compliant with a given configuration.

There are two types of rules:

Here is an example of a simple rule, which we’ll explain in detail later in this guide:

allow {
  input.multi_az == true
}

Here’s an example of an advanced rule, which we’ll also discuss later:

security_groups = fugue.resources("AWS.EC2.SecurityGroup")

policy[r] {
   security_group = security_groups[_]
   security_group.ingress[i].from_port <= 9200
   security_group.ingress[i].to_port >= 9200
   security_group.ingress[i].cidr_blocks[_] == "0.0.0.0/0"
   r = fugue.deny_resource(security_group)
}

How to Create a Custom Rule

To create a custom rule, you first write Rego code defining a pass (allow) or fail (deny) condition for the attribute. Then, you validate and add the rule to Fugue from the Custom Rules page or the API.

To write a custom rule, you must:

  1. Determine the resource attribute(s) you want Fugue to check

  2. Define pass or fail conditions for the resource

Once you’ve successfully written a rule, you can:

See the API Reference for Swagger documentation.

Writing Custom Rules

As stated earlier, you can write custom rules using Rego, an open source query language for Open Policy Agent (OPA).

We won’t detail how to write Rego here, but we encourage you to consult the Rego documentation for the basics.

We’ve added some Fugue-specific functionality to Rego. For details, jump ahead to the Custom Rules Cheat Sheet. Otherwise, continue reading to learn how to write custom rules.

Simple Custom Rules

First, we’ll dive into how to write a simple custom rule, which tests a single resource type.

(Can’t wait to write rules for multiple resources? Feel free to skip ahead to Advanced Custom Rules.)

Step 1: Determine resource attributes (simple)

Once you have a policy in mind that your resources should comply with, you can start writing a custom rule.

To translate policy to code, you’ll need to determine the resource type and attributes that are involved. There are a couple ways to do this:

You can reference the Terraform documentation to determine the resource types and syntax required for AWS and AWS GovCloud and Azure. You’ll find the information you need in the Resource page of each provider resource. For an example, see Resource: aws_security_group.

However, it may be easier to write rules by looking at a concrete example. If you have an environment (or create a new one) that contains the resource type you’re evaluating, you can determine which attributes you need to specify in the rule by using the API to retrieve the results of a recent scan.

Once you have the JSON document containing the scan results, you’ll see the configuration for every resource in the scan. You can then find the attribute in question. See the API Reference for details.

Step 2: Define pass or fail conditions for the resource (simple)

Let’s say your organization requires AWS RDS instances to be deployed in multiple availability zones. If you look at the Terraform RDS docs, you’ll see the aws_db_instance resource lists a multi_az attribute. So, the custom rule should query each AWS.RDS.Instance in an environment and verify that the multi_az property is enabled (i.e., set to true). If so, the resource passes the compliance check. If not, the resource fails.

allow rules

In Fugue, you define a “pass” state by using the allow function. You can define a “fail” state by using the deny function.

For our RDS example, it makes sense to use the allow function, because anything with multi_az set to true should pass the compliance check.

Let’s look at the example again:

allow {
  input.multi_az == true
}

This simple rule contains the following elements:

allow { ... }

This function states that any resource matching the query in the brackets should be assigned a PASS compliance state.

input.multi_az

This element tells Fugue to look in the provided input for the multi_az attribute. When you specify the resource type to assess, Fugue automatically sets the input to that resource type.

== true

If the preceding attribute is true, execute the function. Here, that means Fugue should allow (“pass”) resources that have multi_az set to true.

deny rules

You can also write the rule with the deny function, which accomplishes the same thing by failing any resource where input.multi_az is set to false:

deny {
  input.multi_az == false
}

Another example of a deny function would be a policy that forbids security groups from having port 9200 (Elasticsearch) open to the internet. Any resources matching that query should fail the compliance check. We’ll discuss this use case in the section below.

Multiple queries

So far, all the example rules we’ve shown have single queries, but Rego rules can also consist of multiple queries. These queries can all be required, or some can be required – just like the logical operators AND (&&) and OR (||).

For example, the security group policy we mentioned in the previous section – where port 9200 should not be open to the world – can be written thusly:

deny {
  input.ingress[i].from_port <= 9200
  input.ingress[i].to_port >= 9200
  input.ingress[i].cidr_blocks[_] == "0.0.0.0/0"
}

Let’s dissect this rule:

deny { ... }

This function states that any resource matching the query in the brackets should be assigned a FAIL compliance state.

input.ingress[i].from_port <= 9200

We’ll explain the syntax in more detail in a bit, but this means Fugue should look at the input (security group resources) for the from_port attribute and check whether the value is less than or equal to 9200.

input.ingress[i].to_port >= 9200

Likewise, this line means Fugue should check whether the to_port is greater than or equal to 9200.

input.ingress[i].cidr_blocks[_] == "0.0.0.0/0"

This line means checks whether the cidr_blocks attribute is set to “0.0.0.0/0” (meaning the port is open to the world).

Note that there are three queries. Because they’re all contained in the same deny {} function, all queries must apply in order for the resource to fail the compliance check. You can restate it in pseudocode like this:

IF the ingress port range starts at 9200 or below, AND the ingress port range ends at 9200 or above, AND the ingress CIDR block is “0.0.0.0/0” (open to the internet), THEN deny (fail) the resource.

To demonstrate how Rego rules handle OR logic, we can rewrite the same rule thusly:

deny {
  input.ingress[i].to_port == 9200
  input.ingress[i].from_port == 9200
  input.ingress[i].cidr_blocks[_] == "0.0.0.0/0"
}

deny {
   input.ingress[i].from_port < 9200
   input.ingress[i].from_port > 9200
   input.ingress[i].cidr_blocks[_] == "0.0.0.0/0"
}

Here, there are two deny functions, which means the rule is denied if either function applies.

The first deny function states that if the port range is set to 9200 and the CIDR block source is "0.0.0.0/0", the resource should fail.

The second deny function states that if the port range includes 9200 and the CIDR block source is "0.0.0.0/0", the resource should fail.

Should either of these conditions be met, Fugue will assign the resource a FAIL compliance state.

This is a less efficient way of writing the same rule, but it demonstrates how you can use OR logic in Rego rules.

Advanced Custom Rules

While simple custom rules focus on a single resource type, advanced custom rules look at the entire environment and can therefore involve multiple resource types.

This makes it possible to mix and match different resource types and join them in a variety of ways. Advanced rules are far more expressive than simple rules, but they are also more complex.

The high-level workflow for advanced rules is usually:

  1. Query for resources by using the fugue.resources(resource_type) function.

  2. Declare a policy set rule that iterates over queried resources.

  3. Extend the policy set using one of the following primitives:

  • fugue.allow_resource(resource)

  • fugue.deny_resource(resource)

  • fugue.missing_resource(resource_type)

If we consider the simple rule to check whether security group port 9200 is open to the world, this would be an advanced port of that rule. Even though it checks a single resource type, the use of the functions fugue.resources(resource_type), fugue.allow_resource(resource), and fugue.deny_resource(resource) make it an advanced rule:

# Return all security groups in an environment
security_groups = fugue.resources("AWS.EC2.SecurityGroup")

# Security groups that have port 9200 open to the internet are considered invalid
invalid(sg) {
  sg.ingress[i].from_port <= 9200
  sg.ingress[i].to_port >= 9200
  sg.ingress[i].cidr_blocks[_] == "0.0.0.0/0"
}

# Build policy document; invalid security groups fail, valid ones pass
policy[r] {
   security_group = security_groups[_]
   invalid(security_group)
   r = fugue.deny_resource(security_group)
} {
   security_group = security_groups[_]
   not invalid(security_group)
   r = fugue.allow_resource(security_group)
}

Let’s dig into what these new functions do.

Advanced Rule API

fugue.resources(resource_type)

Returns a collection of resources of the given resource_type string. The custom rules engine also keeps track of which resource types are requested. We use this information to decide whether there are simply no resources of a type, or if we do not have permission to scan that resource type.

fugue.allow_resource(resource)

Makes a PASS judgement about a resource retrieved using fugue.resources.

fugue.deny_resource(resource)

Makes a DENY judgement about a resource retrieved using fugue.resources.

fugue.missing_resource(resource_type)

Makes a DENY judgement about a resource type string without actually having access to a resource. This can be used to write a rule that checks for the presence of a specific resource.

Why do we need more than allow and deny? For example, if we require a CloudTrail alarm for failed logins, it does not mean that other CloudTrail alarms are invalid – they are simply irrelevant. Hence, we should not mark these other CloudTrail alarms as DENY using fugue.deny_resource, but rather use fugue.missing_resource to indicate that a resource is missing.

Expanding an Advanced Custom Rule

We can expand the example above to check only certain security groups based on tags. Here, we only look at security groups with the tag key Stage and value Prod to determine whether any have port 9200 open to the world:

# Return all security groups in an environment, then filter on the tag Stage:Prod
tagged_sgs[tags] = security_group {
   security_groups = fugue.resources("AWS.EC2.SecurityGroup")
   security_group = security_groups[tags]
   security_group.tags.Stage == "Prod"
}

# Security groups that have port 9200 open to the internet are considered invalid
invalid(sg) {
  sg.ingress[i].from_port <= 9200
  sg.ingress[i].to_port >= 9200
  sg.ingress[i].cidr_blocks[_] == "0.0.0.0/0"
}

# Build policy document; of the security groups tagged Stage:Prod, invalid SGs fail, valid ones pass
policy[r] {
   security_group = tagged_sgs[_]
   invalid(security_group)
   r = fugue.deny_resource(security_group)
} {
   security_group = tagged_sgs[_]
   not invalid(security_group)
   r = fugue.allow_resource(security_group)
}

Advanced Custom Rules with Multiple Resource Types

Here’s an example of an advanced custom rule with multiple resource types – in this case, AWS.EC2.Vpc and AWS.EC2.SecurityGroup. This rule checks whether all security groups attached to a production VPC have a specific tag. If the security groups have the correct tag, they pass the compliance check; if not, they fail:

# The following multi-resource type validation checks that all Security Groups
# attached to the production VPC have a Stage tag with the value Prod.
# The production VPC.
prod_vpc = vpc {
  vpcs = fugue.resources("AWS.EC2.Vpc")
  vpc = vpcs[_]
  vpc.tags.Name == "prod-vpc"
}
# Security groups attached to the prod VPC.
prod_security_groups[id] = security_group {
  security_groups = fugue.resources("AWS.EC2.SecurityGroup")
  security_group = security_groups[id]
  security_group.vpc_id == prod_vpc.id
}
# Check that the security group is tagged with {"Stage": "Prod"}.
tagged_security_group(security_group) {
  security_group.tags.Stage == "Prod"
}
# Build policy document.
policy[p] {
  security_group = prod_security_groups[_]
  tagged_security_group(security_group)
  p = fugue.allow_resource(security_group)
} {
  security_group = prod_security_groups[_]
  not tagged_security_group(security_group)
  p = fugue.deny_resource(security_group)
}

For further assistance with advanced custom rules, reach out to support@fugue.co.

Custom Rules Cheat Sheet

When writing a custom rule in Fugue, remember the following tips:

  • Custom rule policies do not require the package declaration at the start of a rule.

  • Simple rules must specify at least one allow or deny rule.

    • When more than one allow or deny rule are given, they are simply “OR”-ed together, following standard Rego semantics.

    • If both allow and deny are specified in the same policy, then deny rules will override allow rules.

  • We’ve provided Fugue-specific functions to make writing advanced rules easier.

Adding Custom Rules

Ready to begin? Below are general instructions for creating custom rules with Fugue.

Getting Started

1. In the Custom Rules page, click the Create New Custom Rule button. The Create New Custom Rule page displays, as shown below.

_images/Create_Custom_Rule_Page.png

2. Enter a name for your rule. The name entered here displays on the Rule or Rule Failed fields on the Compliance pages for your environments. Refer to Viewing Compliance Results for more information.

3. Enter a description for your rule. The description entered here displays on the Description field on the Compliance pages for your environments. Refer to Viewing Compliance Results for more information.

4. Select the Cloud Provider. Based on the cloud provider selected determines the resource types that display from the Resource Types drop-down.

5. Select the Resource Type from the drop-down. This is the resource type that you want your custom rule to evaluate. Refer to Writing Custom Rules for more information.

6. In the rule definition, enter your Rego. Refer to Writing Custom Rules for more information.

7. Optionally, click the Validate Rego button. If your rule has invalid syntax, an error displays, similar to what is shown below. Fix the invalid syntax or you can choose to save your rule and edit it later.

_images/Invalid_Rule_Validate_Endpoint.png
  • If the rule has valid syntax, it displays similar to what is shown below.

_images/Valid_Rule_Validate_Endpoint.png

8. Click the Create Custom Rule button. When created, a custom rule is set to an “enabled” status if the rule has valid syntax and it is automatically applied to all environments on the subsequent scheduled scans.

  • If the rule contains invalid syntax, the following error modal displays similar to what is shown below. You can chose to fix the rule now or later. Custom rules that contain invalid syntax are not automatically ran against your environments until there are no syntax errors and the custom rule is set to an “invalid” status.

_images/Syntax_Error_Modal.png

Note

You can manually kick off a scan for your environment(s) to see the result of your custom rule against the specified environment(s) using the Fugue API. Refer to Initiating a Scan for more information.

Adding Custom Rules – API

To add a custom rule using the API, see Creating a Custom Rule.

You can determine whether a custom rule will work by testing the code against infrastructure state from a particular scan.

To view the infrastructure state in a scan, retrieve the input for a custom rule test. You can also use the infrastructure state JSON to determine the resource attributes and syntax for writing custom rules.

Modifying and Deleting Custom Rules

To edit or delete a custom rule, navigate to the Custom Rules tab and find the ellipsis ... next to the custom rule you want to edit or delete:

_images/Modify_Delete_Custom_Rule.png

Note

You cannot modify the cloud provider for an existing custom rule. If you would like to create a rule with a different cloud provider, you need to create a new custom rule.

Viewing Compliance Results

All custom rules with valid syntax are automatically ran against your environment on the next scheduled scan. After the scheduled scan is ran or kicked off manually via the API, the Custom Compliance Standard displays for your environment. You can filter by Custom to only view the results for your custom rule(s).

_images/Custom_Compliance_Family.png

Additionally, the three compliance tabs allow you to take a deeper dive into why your custom rule is passing or failing.

Modifying and Deleting Custom Rules – API

To update a custom rule using the API, see Updating a Custom Rule.

To delete a custom rule, see Deleting a Custom Rule.

Compliance by Rule

Custom rules display similar to what is shown below. The name you entered for the rule on the Create Custom Rule page displays in the Rule column and the description you entered displays in the Description column.

_images/Compliance_by_Rule.png

Clicking on a failed rule displays similar to what is shown below.

_images/Custom_Rule_Compliance_Modal.png

Compliance by Resource Type

Custom rules display similar to what is shown below on the Compliance by Resource Type tab.

_images/Compliance_by_ResourceType.png

Clicking on a non compliant resource type displays similar to what is shown below. The name you entered for the rule on the Create Custom Rule page displays in the Rule Failed column and the description you entered displays in the Description column.

_images/Custom_Compliance_Family_Modal.png

Compliance by Resource

Custom rules display similar to what is shown below on the Compliance by Resource tab.

_images/Compliance_by_Resource_Custom.png

Clicking on a non compliant resource displays similar to what is shown below.The name you entered for the rule on the Create Custom Rule page displays in the Rule Failed column and the description you entered displays in the Description column.

_images/Compliance_By_Resource_Custom_Rules.png

Example Rules

For example custom rules, see our GitHub repo.

Learning Rego

Below are some resources for learning Rego: