Skip to content

Feature Request: Load policies from a remote OPA bundle server #228

@mostealth

Description

@mostealth

Summary

Add support for loading Rego policies from a remote HTTP bundle server at plugin startup, as an alternative to the existing policy_dir local filesystem option.

Motivation

In team and CI environments, policies are typically centrally managed and distributed via an OPA bundle server (S3, GCS, nginx, or a custom registry). Today, every consumer of tflint-ruleset-opa must either:

  • Commit policy files into each repository and keep them in sync, or
  • Pre-download the bundle as a separate CI step before running tflint

Neither approach is ideal. A native bundle_url option would let the plugin fetch the authoritative policy bundle directly at startup, making centralized policy distribution a first-class concern.

Proposed Configuration

plugin "opa" {
  enabled  = true
  version  = "0.10.0"
  source   = "github.com/terraform-linters/tflint-ruleset-opa"

  bundle_url   = "https://policy-server.example.com/bundles/tflint.tar.gz"
  bundle_token = "..."  # optional, bearer token for auth
}

The bundle_token value should also be resolvable from an environment variable to avoid hardcoding secrets.

Proposed Behaviour

  • If bundle_url is set, the plugin fetches the bundle over HTTP(S) at startup, parses it with bundle.NewReader(), and passes it to rego.New() via rego.ParsedBundle() — exactly the same in-process evaluation path used today
  • If bundle_url is not set, behaviour is identical to today (policy_dir is used)
  • Both can coexist: bundle_url supplies shared org-wide policies, policy_dir supplies local overrides
  • Optional: respect ETag / If-None-Match for caching, to avoid re-downloading an unchanged bundle on every tflint run

What This Is Not

This is not a request for the full OPA bundle polling runtime (plugins/bundle + plugin manager). That machinery is designed for long-lived server processes. For a CLI tool like tflint, a single one-shot HTTP fetch at startup is the right model — simple, fast, and with no persistent state.

Implementation Notes

The change is self-contained and relatively small:

  • Extend Config with BundleURL and BundleToken fields
  • In ApplyConfig, branch on whether BundleURL is set: if so, http.Get the URL, pipe the response body through bundle.NewReader().Read(), and pass the result to NewEngine alongside or instead of the loader.Result
  • NewEngine (or a sibling constructor) accepts a *bundle.Bundle and passes it to rego.New() via rego.ParsedBundle()
  • The custom Rego builtins (terraform.resources, tflint.issue, etc.) are registered on the rego.Rego instance as today — they work regardless of where the policies came from

Roughly ~100 lines of new Go, no new dependencies (all required packages are already present in the OPA module).

Alternatives Considered

  • Pre-downloading the bundle in CI: works but requires an extra step and couples the CI script to the bundle server URL/auth, rather than keeping that in .tflint.hcl
  • Committing policies to each repo: creates drift and makes org-wide policy updates require PRs across every repository

Are you willing to contribute a PR?

Yes, if the approach is agreed upon.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions