Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,37 @@ jobs:
- name: Lint Rule Types
run: go run github.com/mindersec/minder/cmd/dev@latest ruletype lint -r rule-types/github --skip-rego

- name: Install OPA
run: go install github.com/open-policy-agent/opa@v1.15.2

- name: Check embedded Rego formatting
run: python3 scripts/migrate-rego-v1.py --check .

- name: Install PyYAML
run: python3 -m pip install PyYAML==6.0.3

- name: Extract Rego policies
run: >-
python3 scripts/extract-rego.py
--output "${RUNNER_TEMP}/rego"
rule-types
security-baseline/rule-types
autofill-insights/rule-types

# YAML block indentation uses spaces. Restore OPA's canonical tab
# indentation before asking Regal to enforce opa-fmt.
- name: Normalize extracted policies
run: find "${RUNNER_TEMP}/rego" -name '*.rego' -exec opa fmt --write {} +

- name: Enforce Rego v1
run: >-
go run github.com/open-policy-agent/regal@v0.41.1 lint
--disable-all
--enable use-rego-v1
--enable opa-fmt
--config-file .regal/config.yaml
"${RUNNER_TEMP}/rego"

- name: Ensure rule type release_phase is set
run: |
# Directory containing YAML files
Expand Down
12 changes: 12 additions & 0 deletions .regal/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
rules:
imports:
use-rego-v1:
level: error
style:
opa-fmt:
level: error

capabilities:
from:
engine: opa
version: v0.68.0
65 changes: 65 additions & 0 deletions scripts/extract-rego.py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like you're extracting a single (well-known) YAML field from a document here -- this should almost certainly use pyyaml rather than string processing. (Or you could use yq and some shell, but I'm okay with using Python)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I personally like python more, can change it if it's necessary.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to keep using python, but I'd like to use the yaml library for parsing, rather than ad-hoc parsing with regular expressions and python code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will keep this in mind

Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""Extract Rego policies embedded in YAML rule type definitions."""

from __future__ import annotations

import argparse
import pathlib

import yaml


def yaml_files(paths: list[pathlib.Path]) -> list[pathlib.Path]:
files: list[pathlib.Path] = []
for path in paths:
if path.is_dir():
files.extend(path.rglob("*.yaml"))
files.extend(path.rglob("*.yml"))
elif path.suffix in {".yaml", ".yml"}:
files.append(path)
test_suffixes = (
".test.yaml",
".test.yml",
".no-datasource-test.yaml",
".no-datasource-test.yml",
)
return sorted({path for path in files if not path.name.endswith(test_suffixes)})


def extract_policies(path: pathlib.Path) -> list[str]:
contents = yaml.safe_load(path.read_text()) or {}
rego = contents.get("def", {}).get("eval", {}).get("rego", {}).get("def", "")
return [rego] if rego else []


def output_name(path: pathlib.Path, policy_index: int) -> str:
normalized = "__".join(path.parts)
return f"{normalized}.{policy_index}.rego"


def main() -> int:
parser = argparse.ArgumentParser(
description="Extract embedded Rego definitions into standalone files.",
)
parser.add_argument("paths", nargs="+", type=pathlib.Path)
parser.add_argument("--output", required=True, type=pathlib.Path)
args = parser.parse_args()

args.output.mkdir(parents=True, exist_ok=True)

extracted = 0
for path in yaml_files(args.paths):
for policy_index, source in enumerate(extract_policies(path)):
output = args.output / output_name(path, policy_index)
output.write_text(source)
extracted += 1

if extracted == 0:
parser.error("no embedded Rego policies found")

print(f"Extracted {extracted} Rego policies to {args.output}")
return 0


if __name__ == "__main__":
raise SystemExit(main())