Skip to main content

Developing custom IaC rules with Snyk

Written by:

Teodora Sandu

wordpress-sync/blog-feature-snyk-iac-magenta

November 18, 2021

0 mins read

In an increasingly cloud native world, infrastructure as code (IaC) is often the first point of entry into an application. And with technologies such as Kubernetes and Terraform becoming increasingly popular, most app developers will update at least one Kubernetes or Terraform resource at one point in their career.

But while updating and maintaining infrastructure can be as simple as managing a few configuration files — or as complicated as orchestrating a complex architecture with multiple cloud native stacks — one thing holds true: infrastructure security is crucial to app security. And even though many cloud providers and IaC scanning tools have built-in or community-contributed configuration and security rulesets, infrastructure is often too diverse and too complex for a “one-size secures all” approach.

In this article, I’ll walk you through securing your infrastructure environment with Snyk IaC custom rules. Leveraging the Snyk IaC rules (snyk-iac-rules) SDK, you can now write, test, and bundle custom infrastructure rules for the Snyk CLI.

Maximize flexibility with Open Policy Agent (OPA)

Behind the scenes, Snyk IaC uses Open Policy Agent (OPA), an open source general policy agent, and its language Rego to define custom rules. As a framework-agnostic agent, OPA makes it really easy for teams to modify and enforce policies across their cloud native stack, whether it’s for infrastructure, application authorization, or Kubernetes admission control.

Since rules are written in OPA’s native query language, they are also exportable and reusable outside of Snyk. This means any investment you make here can be used across the OPA ecosystem of integrations.

For the walkthrough, you’ll need a basic understanding of OPA, as well as WebAssembly (Wasm) and OCI artifacts, which we use to bundle and distribute the rules.

Secure better with custom rules

Custom rules allow you to cater to every specific use case, making them a powerful tool when combined with prebuilt threat-modeled rulesets from security experts.

As a custom rule example, let’s say I’m a security architect and have a specific internal requirement that all Terraform aws_iam_role resources in our application be tagged with owner, description, and type tags.

This means I’d want to run a rule that:

  • Ensures any existing or future IAM role resources have an owner, description, and type tag

  • Notifies app developers when they forget to add tags.

I’d also want to configure our CI/CD to fail when a code change happens that doesn’t follow this standard, so it doesn’t end up in our production environments.

How would I like to do that? The answer is by configuring the Snyk CLI to run inside my CI/CD with a custom set of rules that I write, bundle, and publish to a safe place of my choice.

Developing a custom IaC rule with Snyk

With the new Snyk IaC rules SDK, I can do exactly that. I install it on my machine and with the help of the custom rules documentation I can quickly get my new tagging standard enforced in the Snyk CLI.

Take as an example the following Terraform resource, which I want to contain our owner, description, and type tags:

1resource "aws_iam_role" "denied" {
2  name = "denied"
3  assume_role_policy = jsonencode({
4      Version   = "2012-10-17"
5      Statement = [
6        {
7          Action    = "sts:AssumeRole"
8          Effect    = "Allow"
9          Sid       = ""
10          Principal = {
11            Service = "ec2.amazonaws.com"
12          }
13        },
14      ]
15  })
16  tags = {
17    description = "a tag that describes something"
18  }
19}

Following the getting started documentation in the custom rules documentation, I run the template command to generate a skeleton rule:

1$ snyk-iac-rules template --rule CUSTOM-RULE-8

I want the tagging standard to be a medium-level severity rule, so I modify the template as such and customize the title and message:

1package rules
2
3aws_iam_role_tags_missing(resource) {
4    not resource.tags.owner
5}
6
7aws_iam_role_tags_missing(resource) {
8    not resource.tags.description
9}
10
11aws_iam_role_tags_missing(resource) {
12    not resource.tags.type
13}
14
15deny[msg] {
16    resource := input.resource.aws_iam_role[name]
17    aws_iam_role_tags_missing(resource)
18
19    msg := {
20        "publicId": "CUSTOM-RULE-8",
21        "title": "IAM Role missing one of the required tags: owner, description or type",
22        "severity": "medium",
23        "msg": sprintf("input.resource.aws_iam_role[%s].tags", [name]),
24        "issue": "",
25        "impact": "",
26        "remediation": "",
27        "references": [],
28    }
29}

Before I bundle and publish the rule, I write some unit tests to verify the behaviour of the rule and put my example Terraform fixture file in the generated ./rules/CUSTOM-RULE-8/fixtures/denied2.tf file:

1package rules
2
3import data.lib
4import data.lib.testing
5
6test_CUSTOM_RULE_8 {
7        # array containing test cases where the rule is allowed
8        allowed_test_cases := []
9
10        # array containing cases where the rule is denied
11        denied_test_cases := [{
12            "want_msgs": ["input.resource.aws_iam_role[denied].tags"],
13            "fixture": "denied2.tf",
14        }]
15
16        test_cases := array.concat(allowed_test_cases, denied_test_cases)
17        testing.evaluate_test_cases("CUSTOM-RULE-8", "./rules/CUSTOM-RULE-8/fixtures", test_cases)
18}

You can find this rule and more in our public GitHub repository.

1$ snyk-iac-rules test
2PASS: 1/1

Once I run my tests and am satisfied with them passing, I bundle the rule using the SDK and can even test it locally with the Snyk CLI by using the --rules flag. First, I make sure to be authenticated with a test-org organization, where I aim to run all of my custom rules experiments from now on:

1$ snyk auth

Then, I build my custom rules bundle and pass it to the --rules flag so that I can make sure my IaC issues get flagged up:

1$ snyk-iac-rules build .
2Generated bundle: bundle.tar.gz
3
4$ snyk iac test --rules=bundle.tar.gz ./fixtures/custom-rules/rules/CUSTOM-RULE-8/fixtures/denied2.tf
5
6Testing ./fixtures/custom-rules/rules/CUSTOM-RULE-8/fixtures/denied2.tf...
7
8Infrastructure as code issues:
9  ✗ IAM Role missing one of the required tags: owner, description or type [Medium Severity] [CUSTOM-RULE-8]
10    introduced by input > resource > aws_iam_role[denied] > tags
11
12Organization:      test-org
13Type:              Terraform
14Target file:       ./fixtures/custom-rules/rules/CUSTOM-RULE-8/fixtures/denied2.tf
15Project name:      fixtures
16Open source:       no
17Project path:      ./fixtures/custom-rules/rules/CUSTOM-RULE-8/fixtures/denied2.tf
18
19Tested ./fixtures/custom-rules/rules/CUSTOM-RULE-8/fixtures/denied2.tf for known issues, found 1 issues

With the confidence that the Snyk CLI can interpret my rule, I push it to an OCI registry, in this case DockerHub, and tag it as the very first version of my custom rules. This way if I deliver any breaking changes in future, I can do a gradual rollout of the rules by using a new tag:

1$ snyk-iac-rules push --r docker.io/snykgoof/oci-example:v1 bundle.tar.gz

Last but not least, I enforce the usage of my new custom rule across all teams in my department by configuring the IaC Settings for my Snyk group through the public Group IaC Settings API.

1curl --location --request PATCH 'https://api.snyk.io/v3/groups/<group_id>/settings/iac/?version=2021-11-03~beta' \
2--header 'Content-Type: application/vnd.api+json' \
3--header 'Authorization: token <API key from Snyk>' \
4--data-raw '{
5   "data": {
6         "type": "iac_settings",
7         "attributes": {
8           "custom_rules": {
9             "oci_registry_url": "https://registry-1.docker.io/snykgoof/oci-example",
10             "oci_registry_tag": "v1",
11             "is_enabled": true
12           }
13       }
14   }
15}'

I have automated all these steps using a GitHub action workflow, as can be seen in our public GitHub repository. Now, any app developer or CI/CD pipeline authenticated with an organization underneath my group will be using my custom rule. For more information about how to integrate the Snyk CLI and the Snyk IaC Rules SDK with GitHub, please read our documentation.

If we decide to apply different custom rules for different parts of the company, we can also do that by overriding the custom rules settings at the organization level and so have a different set of custom rules run for that organization only. For example, in order to continue using the --rules flag to test my custom rules with the Snyk CLI, I have configured my test-org organization to have the custom rules settings disabled. This way, I can provide my own custom rules locally:

wordpress-sync/blog-custom-iac-rules-settings

I may decide in the future that the Platform team needs a more restrictive set of custom rules, so I will configure their organization to use a different custom rules bundle, stored under a different OCI artifact.

Reaping the benefits of Snyk IaC custom rules

We have seen how I, as a security architect, have used the Snyk IaC rules SDK to configure a new set of security standards for my department. Now, we can see how I, as an app developer, benefit from this feature during development time.

Let’s say I am adding a new aws_iam_role resource to our application’s Terraform infrastructure so that I can configure a new IAM role:

1resource "aws_iam_role" "new_role" {
2  name               = "new_role"
3  assume_role_policy = jsonencode({
4    Version   = "2021-11-18"
5    Statement = [
6      {
7        Action    = "sts:AssumeRole"
8        Effect    = "Allow"
9        Sid       = ""
10        Principal = {
11          Service = "ec2.amazonaws.com"
12        }
13      },
14    ]
15  })
16}

I have tested this locally and verified that my IAM role was configured correctly. However, I have forgotten that my department requires very strict tagging for all our resources. As I go to open a PR though, I can see that the repository I’m pushing to has been configured to run the Snyk IaC GitHub Action and is failing.

wordpress-sync/blog-custom-iac-rules-fail

I look at the results and notice that my new file is causing the PR check to fail, more specifically that my resource is missing an owner, description, and type tag.

wordpress-sync/blog-custom-iac-rules-results

I go back to my code and add the missing tags and now after I push my change and the PR check reruns, I can safely and confidently push the new resource to our production environment!

Securing your applications with Snyk IaC

Snyk Infrastructure as Code (Snyk IaC) helps you develop fast while staying secure by providing developers with the security intelligence and in-line fixes for Terraform, CloudFormation, Kubernetes configurations, and ARM templates that can be directly merged into code.

Snyk IaC works where developers do, integrating with systems like Terraform Cloud and CI/CD tools as well as developer tools like source code management (GitHub, GitLab, Bitbucket) and IDEs.

You can get started now for free with Snyk IaC on our platform or through downloading the latest Snyk CLI.

wordpress-sync/blog-feature-snyk-iac-magenta

Snyk Top 10: Vulnerabilites you should know

Find out which types of vulnerabilities are most likely to appear in your projects based on Snyk scan results and security research.