Blog

Extending Minder to create custom rule types for GitHub repo security

Minder helps development teams and open source communities build more secure software. We've curated a set of rule types and profiles that you can use to proactively manage your GitHub security posture. However, you can also write your own custom rule types.

/
9 mins read
/
Dec 5, 2023
Minder

Stacklok’s cookbook: Minder rule types á la Ozz

Hello everybody! I hope you’re as excited as I am about the open-sourcing of Minder.

Minder helps development teams and open source communities build more secure software. Specifically, with Minder, you can automate the enforcement of security settings and best practices across your GitHub repos. To help you get started, we’ve curated a set of rule types and profiles that you can use to proactively manage your security posture. However, you can also write your own custom rule types.

Today we’ll learn to write cook a new rule type, and what better way to do it than with a setting that I find quite handy! Before diving into this though, let’s recap some basic concepts in Minder.

Minder policies

Minder allows you to check and enforce that certain settings are set up for several stages in your supply chain. To configure those settings, you need to create a Profile. This profile is composed of several rules that represent the settings you want in your supply chain. These rules are actually instantiations of another handy object for Minder called Rule Types. These rule types define the nitty-gritty details of how the specific setting you care about will be checked, how you’ll be alerted when something goes out of order, and how it will be automatically remediated (yes, Minder can do all of this now!).

You can browse a curated collection of rule types in our rules and profiles repository. You may want to check all sorts of things! Some of the rules we’ve curated include:

  • Verifying if you have GitHub’s secret scanning enabled

  • Verifying if your artifacts are signed and pushed to Sigstore

  • Verifying that your branch protection settings are secure

More on Rule Types / Dish Structure

Rule types aren’t particularly complicated. They include the basic structure to get an observed state, evaluate the rule based on that observed state, do actions based on that state, and finally, give you some instructions in case you want to manage things manually.

The Rule Type object in YAML looks as follows:

Yaml
---
version: v1
type: rule-type
name: my_cool_new_rule
context:
  provider: github
description: // Description goes here
guidance: // Guidance goes here
def:
  in_entity: repository  // what are we evaluating?
  param_schema: // parameters go here
  rule_schema: // rule definition schema goes here
  # Defines the configuration for ingesting data relevant for the rule
  ingest: // observed state gets fetched here
  eval: // evaluation goes here
  remediation: // fixing the issue goes here
  alert: // alerting goes here

In our case, we know that we’ll evaluate this rule for GitHub, so we know the context of evaluation. Minder will support more providers in the future, so the provider setting will become more relevant. For now, it’s an easy decision!

Let’s go through a quick overview of all of the components of a rule type:

  • Description: What does the rule do? This is handy to browse through rule types when building a profile.

  • Guidance: What do I do if this rule presents a “failure”? This is handy to inform folks of what to do in case they’re not using automated remediations.

  • in_entity: What are we evaluating? This defines the entity that’s being evaluated. It could be repository, artifact, pull_request, and build_environment (coming soon).

  • param_schema: Optional fields to pass to the ingestion (more on this later). This is handy if we need extra data to get the observed state of an entity.

  • rule_schema: Optional fields to pass to the evaluation (more on this later). This is handy for customizing how a rule is evaluated.

  • Ingest: This step defines how we get the observed state for an entity. It could be a simple REST call, a cloning of a git repo, or even a custom action if it’s a complex rule.

  • Eval: This is the evaluation stage, which defines the actual rule evaluation.

  • Remediation: How do we fix the issue? This defines the action to be taken when we need to fix an issue. This is what happens when you enable automated remediations.

  • Alert: How do we notify folks about the issue? This may take the form of a GitHub Security Advisory, but we’ll support more alerting systems in the near future.

With this in mind, let’s write a rule type!

Today’s rule type: Automatically delete head branches

Today, we’ll write a rule type for checking that GitHub automatically deletes branches after a pull request has been merged. While this is not strictly a security setting, it is a good practice to keep your branches clean to avoid confusion.

Ingredients

  • 1 text editor

  • A pinch of Git

  • 1 GitHub account

  • 1 GitHub repository

  • 1 Stacklok account

  • 1 Minder CLI

For all of the steps in our dish, we’ll keep the rule type structure described above in mind.

Protein: Ingestion / Evaluation

The first thing we need to figure out is how to get the observed state of what we want to evaluate on. This is the ingestion part.

Fortunately for us, GitHub keeps up-to-date and extensive documentation on their APIs. A quick internet search leads us to the relevant Repositories API where we can see that a call to the /repos/OWNER/REPO endpoint gives us the following key: delete_branch_on_merge.

So, by now we know that we may fetch this information via a simple REST call.

The ingestion piece would then look as follows:

Yaml
---
def:
  ...
  ingest:
    type: rest
    rest:
      endpoint: "/repos/{{.Entity.Owner}}/{{.Entity.Name}}"
      parse: json

While you could hard-code the user/org and name of the repository you want to evaluate, that kind of rule is not handy, especially if you want to enroll multiple repositories in Minder. Thus, Minder has a templating system that allows you to base multiple parts of the rule type on the entity you’re evaluating (remember the in_entity part of the rule type?). The fields you may use are part of the entity’s protobuf, which can be found in our documentation.

Now, we want to tell Minder what to actually evaluate from that state. This is the evaluation step. In our case, we want to verify that delete_branch_on_merge is set to true. For our intent, we have a very simple evaluation driver that will do the trick just fine! That is the jq evaluation type.

I understand this is not a setting that everybody would want, and, in fact, some folks might want that setting to be off. This is something we can achieve with a simple toggle. To do it, we need to add a rule_schema to our rule, which would allow us to have a configurable setting in our rule.

The evaluation would look as follows:

Yaml
---
def:
  rule_schema:
    type: object
    properties:
      enabled:
        type: boolean
    required:
      - enabled
  eval:
    type: jq
    jq:
    - ingested:
        def: '.delete_branch_on_merge'
      profile:
        def: ".enabled"

The rule type above now allows us to compare the delete_branch_on_merge setting we got from the GitHub call, and evaluate it against the enabled setting we’ve registered for our rule type.

Side: Alerting

To complement our protein, we’ll now describe how you may get a notification if your repository doesn’t adhere to the rule. This is as simple as adding the following to the manifest:

Yaml
---
def:
  alert:
    type: security_advisory
    security_advisory:
      severity: "low"

This will create a security advisory in your GitHub repository that you’ll be able to browse for information. Minder knows already what information to fill-in to make the alert relevant!

Sauce: Remediation

Minder has the ability to auto-fix issues that it finds in your supply chain, let’s add an automated fix to this rule! Similarly to ingestion, remediations also have several flavors or types. For our case, a simple REST remediation suffices.

Let’s see how it would look:

Yaml
---
def:
  remediate:
    type: rest
    rest:
      method: PATCH
      endpoint: "/repos/{{.Entity.Owner}}/{{.Entity.Name}}"
      body: |
        { "delete_branch_on_merge": {{ .Profile.enabled }} }

This effectively would do a PATCH REST call to the GitHub API if it finds that the rule is out of compliance. We’re able to parametrize the call with whatever we defined in the profile using golang templates (that’s the {{ .Profile.enabled }} section you see in the message’s body.

Presentation: Description & Guidance

Before running our rule and proposing it to the community, we need to dish it! There are a couple of sections that allow us to give information to rule type users about the rule and what to do with it. These are the description and guidance. The description is simply a textual representation of what the rule type should do. Guidance is the text that will show up if the rule fails. Guidance is relevant when automatic remediations are not enabled, and we want to give folks instructions on what to do to fix the issue.

For our rule, they will look as follows:

Yaml
---
version: v1
type: rule-type
name: my_cool_new_rule
context:
  provider: github
description: |
  This rule verifies that branches are deleted automatically once a pull
  request merges.
guidance: |
  To manage whether branches should be automatically deleted for your repository
  you need to toggle the "Automatically delete head branches" setting in the
  general configuration of your repository.

  For more information, see the GitHub documentation on the topic: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-the-automatic-deletion-of-branches

Degustation: Trying the rule out

The whole rule can be seen in the Rules and Profiles GitHub repository. In order to try it out, we’ll use the minder CLI, which points to the Minder server hosted by your friends at Stacklok.

Before continuing, make sure you use our Quickstart to install the CLI and enroll your GitHub repos.

Let’s create the rule:

Shell (Bash)
$ minder rule_type create -f rules/github/automatic_branch_deletion.yaml                                   

Here, you can already see how the description gets displayed. This same description will be handy when browsing rules through minder rule_type list.

Shell (Bash)
| PROVIDER | PROJECT NAME | ID | NAME | DESCRIPTION |
| github   | jaormx   	| b643ea30-d2e7-46d0-b0a3-ab8814fba855 | automatic_branch_deletion | This rule verifies that branches are deleted automatically once a pull request merges.  

Let’s now try it out! We can call our rule in a profile as follows:

Yaml
---
version: v1
type: profile
name: degustation-profile
context:
  provider: github
alert: "on"
remediate: "off"
repository:
  - type: automatic_branch_deletion
    def:
      enabled: true

We’ll call this degustation-profile.yaml. Let’s create it!

Shell (Bash)
$  minder profile create -f degustation-profile.yaml

For the repository I’m currently testing with, the rule will fail. So, if you browse the security advisories of the repository, you’ll notice that a new one was created for this:

Minder profile failure

While I could solve the issue myself using the guidance that was provided, I’ll instead get Minder to do this for me. We’ll modify the profile to look as follows:

Yaml
---
version: v1
type: profile
name: degustation-profile
context:
  provider: github
alert: "on"
remediate: "on"
repository:
  - type: automatic_branch_deletion
    def:
      enabled: true

The only thing we’ve changed is that we’ve set the remediate field to on. Let’s update our profile to reflect this:

Shell (Bash)
$ minder profile update -f degustation-profile.yaml

Now, when we look at our repository settings, we’ll already see the following:

Minder profile update

And the aforementioned advisory will be resolved.

Where do we go from here?

Now that we’ve created a new rule and tested it in a profile, let’s contribute them to our community repository!

The rules and profiles community repo contains a set of reference rules maintained by your friends at Stacklok. We welcome contributions and encourage you to do so. For reference, here's an example pull request adding the new rule type and referencing it from a profile looks as follows.

Conclusion

We’ve now created a basic new rule for Minder. There are more ingestion types, rule evaluation engines, and remediation types that we can use today, and there will be more in the future! But that’s for another blog post.

--

Note: You can now install the minder CLI, enroll your repos, and create your first rule type in less than 1 minute using our new Quickstart. Try it out!