Logic Rules (OPA)

You can integrate Code Security with the Open Policy Agent (OPA) project such that it can use its logic facilities.

Rego in Code Security

A generic schema of a Code Security-compatible Rego policy:

Copy
package opa_rule

Policy[result] {
    < LOGIC >

    result := {
        "id": "CR-AF001",
        # search expression to detect line numbers when needed
        # "keysearch": "",
        "severity": "WARNING",
        "text": "Airflow encryption scheme should be more strict",
        # just a reference URL for playbooks, and additional information
        "url": "https://example.com"
    }
}

Best practice for building detectors based on Rego:Principles:

  • Every policy returns a result.

  • Every result has a clear and strict schema, which Code Security requires to create a finding.

  • Multiple policies can co-exist in the same rule, as long as each of those have a different ID.

Notes:

  • The package opa_rule is mandatory.

  • The result := and its assigned document are mandatory.

Building a Custom Code Security Detector with OPA

Code Security supports two different match types:

  • opa_inline - pointing to a rego rule inlined inside the yaml, in the pattern field. You must specify parser with params.parser.

  • opa_file - pointing to a physical file on disk. pattern spec is parser:namespace:file-relative-to-spectral.yaml.

Example:

Copy
rules:
- id: AF001
  tags:
  - base # activate by default, part of the base package
  applies_to:
  - ".*\\.conf$"
  severity: info
  pattern_group:
    aggregate: or
    scope: text
    patterns:
    # remember: rego file must declare package 'opa_rule'
    # <parser>:<policy name>:<path to rego file>
    - pattern: "ini:airflow-main-configuration:./af.rego"
      pattern_type: opa_file
      test_regex_prematch:
      # this detects that the configuration file is *actually* Airflow related
      - pattern: "airflow_"

- id: AF002
  tags:
  - base # activate by default, part of the base package
  applies_to:
  - ".*\\.conf$"
  severity: info
  pattern_group:
    aggregate: or
    scope: text
    patterns:
    # pattern is the actual rego policy, and we specify a parser in 'params:' later below
    - pattern_type: opa_inline
      params:
        parser: ini
      test_regex_prematch:
      # this detects that the configuration file is *actually* Airflow related
      - pattern: "airflow_"
      pattern: >
        package opa_rule

        Policy[result] {
          true # <some logic>
          result := {
            "id": "AF002",
            "severity": "WARNING",
            "text": "Airflow encryption seed value should not be visible",
            "url": "https://example.com",
          }
        }

Rule Settings

Code Security's OPA rules are a combination of:

  • Routing - the concept of detecting configuration types and pointing to the correct parser and policy bundle.

  • Logic - Defined through Rego.

  • Rule settings (rule_settings) - Overlay of settings to allow for user customization.

Rule setting example:

Copy
RuleSettings{
  shared: json_value
  rules: json_value
}

Corresponding yaml file example:

Copy
rule_settings:
  shared:
    whitelist_email: ".*"
  rules:
    CR010:
      min_committers: 3

Usage in rules is optional and depends on your requirement to customize settings for your rules. If you want to use rules, then we recommend this pragma to set up rule settings inside the Rego policy:

Copy
package opa_rule

_s := object.get(input, "__rule_settings__", {})

rules := object.get(_s, "rules", {})

shared := object.get(_s, "shared", {})

Policy[result] {
    rule_id := "ELA001"
    settings = object.get(rules, rule_id, {})
    port := object.get(settings, "port", 9200)

    not input.play.server.http.port = port
    result := {
        "id": rule_id,
        "severity": "WARNING",
        "text": "Elastic should always be on port 9200",
        "url": "https://example.com",
    }
}

To override the port value to a different value, use this rule settings in spectral.yaml:

Copy
rule_settings:
  rules:
    ELA001:
      port: 20200

Keysearch

Use keysearch for configuration that are unpredictable in its shape to find a line number.

No capture

Copy
"keysearch": "some-string-to-find"

Capture

Typically used with multi-line. Enable multiline with (?s) flag.

Copy
"keysearch": "(?s)foo.*bar(capture-this = .*?)andthat"