Git Product home page Git Product logo

regal's Introduction

Regal

Build Status OPA v0.63.0

Regal is a linter and language server for Rego, helping you write better policies and have fun while doing it!

illustration of a viking representing the Regal logo

regal

adj : of notable excellence or magnificence : splendid

- Merriam Webster

Goals

  • Identify common mistakes, bugs and inefficiencies in Rego policies, and suggest better approaches
  • Provide advice on best practices, coding style, and tooling
  • Allow users, teams and organizations to enforce custom rules on their policy code

Regal rules are to as large extent as possible written in Rego themselves, using the JSON representation of the Rego abstract syntax tree (AST) as input, a few additional custom built-in functions and some indexed data structures to help with linting.

What People Say About Regal

I really like that at each release of Regal I learn something new! Of all the linters I'm exposed to, Regal is probably the most instructive one.

— Leonardo Taccari, NetBSD

Reviewing the Regal rules documentation. Pure gold.

— Dima Korolev, Miro

Such an awesome project!

— Shawn McGuire, Atlassian

I am really impressed with Regal. It has helped me write more expressive and deterministic Rego.

— Jimmy Ray, Boeing

See the adopters file for more Regal users.

Getting Started

Download Regal

MacOS and Linux

brew install styrainc/packages/regal
Manual download options

MacOS (Apple Silicon)

curl -L -o regal "https://github.com/StyraInc/regal/releases/latest/download/regal_Darwin_arm64"

MacOS (x86_64)

curl -L -o regal "https://github.com/StyraInc/regal/releases/latest/download/regal_Darwin_x86_64"

Linux (x86_64)

curl -L -o regal "https://github.com/StyraInc/regal/releases/latest/download/regal_Linux_x86_64"
chmod +x regal

Windows

curl.exe -L -o regal.exe "https://github.com/StyraInc/regal/releases/latest/download/regal_Windows_x86_64.exe"

Docker

docker pull ghcr.io/styrainc/regal:latest

See all versions, and checksum files, at the Regal releases page, and published Docker images at the packages page.

Try it out!

First, author some Rego!

policy/authz.rego

package authz

import rego.v1

default allow = false

allow if {
    isEmployee
    "developer" in input.user.roles
}

isEmployee if regex.match("@acmecorp\\.com$", input.user.email)

Next, run regal lint pointed at one or more files or directories to have them linted.

regal lint policy/
Rule:         	non-raw-regex-pattern
Description:  	Use raw strings for regex patterns
Category:     	idiomatic
Location:     	policy/authz.rego:12:27
Text:         	isEmployee if regex.match("@acmecorp\\.com$", input.user.email)
Documentation:	https://docs.styra.com/regal/rules/idiomatic/non-raw-regex-pattern

Rule:         	use-assignment-operator
Description:  	Prefer := over = for assignment
Category:     	style
Location:     	policy/authz.rego:5:1
Text:         	default allow = false
Documentation:	https://docs.styra.com/regal/rules/style/use-assignment-operator

Rule:         	prefer-snake-case
Description:  	Prefer snake_case for names
Category:     	style
Location:     	policy/authz.rego:12:1
Text:         	isEmployee if regex.match("@acmecorp\\.com$", input.user.email)
Documentation:	https://docs.styra.com/regal/rules/style/prefer-snake-case

1 file linted. 3 violations found.

Note If you're running Regal on an existing policy library, you may want to disable the style category initially, as it will likely generate a lot of violations. You can do this by passing the --disable-category style flag to regal lint.

GitHub Actions

If you'd like to run Regal in GitHub actions, please consider using setup-regal. A simple .github/workflows/lint.yml to run regal on PRs could look like this, where policy contains Rego files:

name: Regal Lint
on:
  pull_request:
jobs:
  lint-rego:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: StyraInc/setup-regal@v1
      with:
        # For production workflows, use a specific version, like v0.16.0
        version: latest

    - name: Lint
      run: regal lint --format=github ./policy

Please see setup-regal for more information.

Rules

Regal comes with a set of built-in rules, grouped by category.

  • bugs: Common mistakes, potential bugs and inefficiencies in Rego policies.
  • custom: Custom, rules where enforcement can be adjusted to match your preferences.
  • idiomatic: Suggestions for more idiomatic constructs.
  • imports: Best practices for imports.
  • style: Rego Style Guide rules.
  • testing: Rules for testing and development.

The following rules are currently available:

Category Title Description
bugs constant-condition Constant condition
bugs deprecated-builtin Avoid using deprecated built-in functions
bugs duplicate-rule Duplicate rule
bugs if-empty-object Empty object following if
bugs inconsistent-args Inconsistently named function arguments
bugs invalid-metadata-attribute Invalid attribute in metadata annotation
bugs not-equals-in-loop Use of != in loop
bugs redundant-existence-check Redundant existence check
bugs rule-named-if Rule named "if"
bugs rule-shadows-builtin Rule name shadows built-in
bugs top-level-iteration Iteration in top-level assignment
bugs unassigned-return-value Non-boolean return value unassigned
bugs zero-arity-function Avoid functions without args
custom forbidden-function-call Forbidden function call
custom naming-convention Naming convention violation
custom one-liner-rule Rule body could be made a one-liner
custom prefer-value-in-head Prefer value in rule head
idiomatic boolean-assignment Prefer if over boolean assignment
idiomatic custom-has-key-construct Custom function may be replaced by in and object.keys
idiomatic custom-in-construct Custom function may be replaced by in keyword
idiomatic equals-pattern-matching Prefer pattern matching in function arguments
idiomatic no-defined-entrypoint Missing entrypoint annotation
idiomatic non-raw-regex-pattern Use raw strings for regex patterns
idiomatic prefer-set-or-object-rule Prefer set or object rule over comprehension
idiomatic use-contains Use the contains keyword
idiomatic use-if Use the if keyword
idiomatic use-in-operator Use in to check for membership
idiomatic use-some-for-output-vars Use some to declare output variables
imports avoid-importing-input Avoid importing input
imports circular-import Circular import
imports ignored-import Reference ignores import
imports implicit-future-keywords Use explicit future keyword imports
imports import-after-rule Import declared after rule
imports import-shadows-builtin Import shadows built-in namespace
imports import-shadows-import Import shadows another import
imports prefer-package-imports Prefer importing packages over rules
imports redundant-alias Redundant alias
imports redundant-data-import Redundant import of data
imports unresolved-import Unresolved import
imports use-rego-v1 Use import rego.v1
performance with-outside-test-context with used outside test context
style avoid-get-and-list-prefix Avoid get_ and list_ prefix for rules and functions
style chained-rule-body Avoid chaining rule bodies
style default-over-else Prefer default assignment over fallback else
style default-over-not Prefer default assignment over negated condition
style detached-metadata Detached metadata annotation
style double-negative Avoid double negatives
style external-reference Reference to input, data or rule ref in function body
style file-length Max file length exceeded
style function-arg-return Function argument used for return value
style line-length Line too long
style no-whitespace-comment Comment should start with whitespace
style opa-fmt File should be formatted with opa fmt
style prefer-snake-case Prefer snake_case for names
style prefer-some-in-iteration Prefer some .. in for iteration
style rule-length Max rule length exceeded
style rule-name-repeats-package Rule name repeats package
style todo-comment Avoid TODO comments
style unconditional-assignment Unconditional assignment in rule body
style unnecessary-some Unnecessary use of some
style use-assignment-operator Prefer := over = for assignment
style yoda-condition Yoda condition
testing dubious-print-sprintf Dubious use of print and sprintf
testing file-missing-test-suffix Files containing tests should have a _test.rego suffix
testing identically-named-tests Multiple tests with same name
testing metasyntactic-variable Metasyntactic variable name
testing print-or-trace-call Call to print or trace function
testing test-outside-test-package Test outside of test package
testing todo-test TODO test encountered

By default, all rules except for those in the custom category are currently enabled.

Aggregate Rules

Most Regal rules will use data only from a single file at a time, with no consideration for other files. A few rules however require data from multiple files, and will therefore collect, or aggregate, data from all files provided for linting. These rules are called aggregate rules, and will only be run when there is more than one file to lint, such as when linting a directory or a whole policy repository. One example of such a rule is the prefer-package-imports rule, which will aggregate package names and imports from all provided policies in order to determine if any imports are pointing to rules or functions rather than packages. You normally won't need to care about this distinction other than being aware of the fact that some linter rules won't be run when linting a single file.

If you'd like to see more rules, please open an issue for your feature request, or better yet, submit a PR! See the custom rules page for more information on how to develop your own rules, for yourself or for inclusion in Regal.

Custom Rules

The custom category is a special one, as the rules in this category allow you to enforce rules that are specific to your project, team or organization. This typically includes things like naming conventions, where you might want to ensure that, for example, all package names adhere to an organizational standard, like having a prefix matching the organization name.

Since these rules require configuration provided by the user, or are more opinionated than other rules, they are disabled by default. In order to enable them, see the configuration options available for each rule for how to configure them according to your requirements.

For more advanced requirements, see the guide on writing custom rules in Rego.

Configuration

A custom configuration file may be used to override the default configuration options provided by Regal. The most common use case for this is to change the severity level of a rule. These three levels are available:

  • ignore — disable the rule entirely
  • warning — report the violation without changing the exit code of the lint command
  • error — report the violation and have the lint command exit with a non-zero exit code (default)

Additionally, some rules may have configuration options of their own. See the documentation page for a rule to learn more about it.

.regal/config.yaml

rules:
  style:
    todo-comment:
      # don't report on todo comments
      level: ignore
    line-length:
      # custom rule configuration
      max-line-length: 100
      # warn on too long lines, but don't fail
      level: warning
    opa-fmt:
      # not needed as error is the default, but
      # being explicit won't hurt
      level: error
      # files can be ignored for any individual rule
      # in this example, test files are ignored
      ignore:
        files:
          - "*_test.rego"
  custom:
    # custom rule configuration
    naming-convention:
      level: error
      conventions:
        # ensure all package names start with "acmecorp" or "system"
        - pattern: '^acmecorp\.[a-z_\.]+$|^system\.[a-z_\.]+$'
          targets:
            - package

capabilities:
  from:
    # optionally configure Regal to target a specific version of OPA
    # this will disable rules that has dependencies to e.g. built-in
    # functions or features not supported by the given version
    #
    # if not provided, Regal will use the capabilities of the latest
    # version of OPA available at the time of the Regal release
    engine: opa
    version: v0.58.0

ignore:
  # files can be excluded from all lint rules according to glob-patterns
  files:
    - file1.rego
    - "*_tmp.rego"

Regal will automatically search for a configuration file (.regal/config.yaml) in the current directory, and if not found, traverse the parent directories either until either one is found, or the top of the directory hierarchy is reached. If no configuration file is found, Regal will use the default configuration.

A custom configuration may be also be provided using the --config-file/-c option for regal lint, which when provided will be used to override the default configuration.

Ignoring Rules

If one of Regal's rules doesn't align with your team's preferences, don't worry! Regal is not meant to be the law, and some rules may not make sense for your project, or parts of it. Regal provides several different methods to ignore rules with varying precedence. The available methods are (ranked highest to lowest precedence):

In summary, the CLI flags will override any configuration provided in the file, and inline ignore directives for a specific line will override any other method.

It's also possible to ignore messages on a per-file basis. The available methods are (ranked High to Lowest precedence):

Ignoring a Rule In Config

If you want to ignore a rule, set its level to ignore in the configuration file:

rules:
  style:
    prefer-snake-case:
      # At example.com, we use camel case to comply with our naming conventions
      level: ignore

Ignoring a Category In Config

If you want to ignore a category of rules, set its default level to ignore in the configuration file:

rules:
  style:
    default:
      level: ignore

Ignoring All Rules In Config

If you want to ignore all rules, set the default level to ignore in the configuration file:

rules:
  default:
    level: ignore
  # then you can re-enable specific rules or categories
  testing:
    default:
      level: error
  style:
    opa-fmt:
      level: error

Tip: providing a comment on ignored rules is a good way to communicate why the decision was made.

Ignoring a Rule in Some Files

You can use the ignore attribute inside any rule configuration to provide a list of files, or patterns, that should be ignored for that rule:

rules:
  style:
    line-length:
      level: error
      ignore:
        files:
          # ignore line length in test files to accommodate messy test data
          - "*_test.rego"
          # specific file used only for testing
          - "scratch.rego"

Ignoring Files Globally

If you want to ignore certain files for all rules, you can use the global ignore attribute in your configuration file:

ignore:
  files:
    - file1.rego
    - "*_tmp.rego"

Inline Ignore Directives

If you'd like to ignore a specific violation in a file, you can add an ignore directive above the line in question, or alternatively on the same line to the right of the expression:

package policy

import rego.v1

# regal ignore:prefer-snake-case
camelCase := "yes"

list_users contains user if { # regal ignore:avoid-get-and-list-prefix
    some user in data.db.users
    # ...
}

The format of an ignore directive is regal ignore:<rule-name>,<rule-name>..., where <rule-name> is the name of the rule to ignore. Multiple rules may be added to the same ignore directive, separated by commas.

Note that at this point in time, Regal only considers the same line or the line following the ignore directive, i.e. it does not apply to entire blocks of code (like rules, functions or even packages). See configuration if you want to ignore certain rules altogether.

Ignoring Rules via CLI Flags

For development and testing, rules or classes of rules may quickly be enabled or disabled using the relevant CLI flags for the regal lint command:

  • --disable-all disables all rules
  • --disable-category disables all rules in a category, overriding --enable-all (may be repeated)
  • --disable disables a specific rule, overriding --enable-all and --enable-category (may be repeated)
  • --enable-all enables all rules
  • --enable-category enables all rules in a category, overriding --disable-all (may be repeated)
  • --enable enables a specific rule, overriding --disable-all and --disable-category (may be repeated)
  • --ignore-files ignores files using glob patterns, overriding ignore in the config file (may be repeated)

Note: all CLI flags override configuration provided in file.

Capabilities

By default, Regal will lint your policies using the capabilities of the latest version of OPA known to Regal (i.e. the latest version of OPA at the time Regal was released). Sometimes you might want to tell Regal that some rules aren't applicable to your project (yet!). As an example, if you're running OPA v0.46.0, you likely won't be helped by the custom-has-key rule, as it suggests using the object.keys built-in function introduced in OPA v0.47.0. The opposite could also be true — sometimes new versions of OPA will invalidate rules that applied to older versions. An example of this is the upcoming introduction of import rego.v1, which will make implicit-future-keywords obsolete, as importing rego.v1 automatically imports all "future" functions.

Capabilities help you tell Regal which features to take into account, and rules with dependencies to capabilities not available or not applicable in the given version will be skipped.

If you'd like to target a specific version of OPA, you can include a capabilities section in your configuration, providing either a specific version of an engine (currently only opa supported):

capabilities:
  from:
    engine: opa
    version: v0.58.0

You can also choose to import capabilities from a file:

capabilities:
  from:
    file: build/capabilities.json

You can use plus and minus to add or remove built-in functions from the given set of capabilities:

capabilities:
  from:
    engine: opa
    version: v0.58.0
  minus:
    builtins:
      # exclude rules that depend on the http.send built-in function
      - name: http.send
  plus:
    builtins:
      # make Regal aware of a custom "ldap.query" function
      - name: ldap.query
        type: function
        decl:
          args:
            - type: string
        result:
          type: object

Exit Codes

Exit codes are used to indicate the result of the lint command. The --fail-level provided for regal lint may be used to change the exit code behavior, and allows a value of either warning or error (default).

If --fail-level error is supplied, exit code will be zero even if warnings are present:

  • 0: no errors were found
  • 0: one or more warnings were found
  • 3: one or more errors were found

This is the default behavior.

If --fail-level warning is supplied, warnings will result in a non-zero exit code:

  • 0: no errors or warnings were found
  • 2: one or more warnings were found
  • 3: one or more errors were found

Output Formats

The regal lint command allows specifying the output format by using the --format flag. The available output formats are:

  • pretty (default) - Human-readable table-like output where each violation is printed with a detailed explanation
  • compact - Human-readable output where each violation is printed on a single line
  • json - JSON output, suitable for programmatic consumption
  • github - GitHub workflow command output, ideal for use in GitHub Actions. Annotates PRs and creates a job summary from the linter report
  • sarif - SARIF JSON output, for consumption by tools processing code analysis reports

OPA Check and Strict Mode

Linting with Regal assumes syntactically correct Rego. If there are errors parsing any files during linting, the process is aborted and any parser errors are logged similarly to OPA. OPA itself provides a "linter" of sorts, via the opa check comand and its --strict flag. This checks the provided Rego files not only for syntax errors, but also for OPA strict mode violations.

Note It is recommended to run opa check --strict as part of your policy build process, and address any violations reported there before running Regal. Why both commands? Couldn't the strict mode checks be integrated in Regal? That would certainly be an option. However, most of the strict mode checks will be made default / mandatory as part of a future OPA 1.0 release, at which point they'd be made immediately obsolete as part of Regal. There are a few strict mode checks that likely will remain optional in OPA, and we may choose to integrate them into Regal in the future.

Until then, the recommendation is to run both opa check --strict and regal lint as part of your policy build and test process.

Regal Language Server

In order to support linting directly in editors and IDE's, Regal implements parts of the Language Server Protocol (LSP). With Regal installed and available on your $PATH, editors like VS Code (using the OPA extension) can leverage Regal for diagnostics, i.e. linting, and have the results displayed directly in your editor as you work on your Rego policies. The Regal LSP implementation doesn't stop at linting though — it'll also provide features like tooltips on hover, go to definition, and document symbols helping you easily navigate the Rego code in your workspace.

The Regal language server currently supports the following LSP features:

  • Diagnostics (linting)
  • Hover (for inline docs on built-in functions)
  • Go to definition (ctrl/cmd + click on a reference to go to definition)
  • Folding ranges (expand/collapse blocks, imports, comments)
  • Document and workspace symbols (navigate to rules, functions, packages)
  • Inlay hints (show names of built-in function arguments next to their values)
  • Formatting
  • Code actions (quick fixes for linting issues)

See the editor Support page for information about Regal support in different editors.

Resources

Documentation

Talks

Regal the Rego Linter, CNCF London meetup, June 2023 Regal the Rego Linter

Blogs and Articles

Status

Regal is currently in beta. End-users should not expect any drastic changes, but any API may change without notice. If you want to embed Regal in another project or product, please reach out!

Roadmap

The roadmap for Regal currently looks like this:

  • 50 rules!
  • Add custom (or organizational, opinionated, or...) category for built-in "custom", or organizational rules, to enforce things like naming conventions. The most common customizations should not require writing custom rules, but be made available in configuration.
  • Simplify custom rules authoring by providing command for scaffolding
  • Make more rules consider nested AST nodes
  • GitHub Action

The roadmap is updated when all the current items have been completed.

Community

For questions, discussions and announcements related to Styra products, services and open source projects, please join the Styra community on Slack!

regal's People

Contributors

adam-moss avatar anderseknert avatar c-wygoda avatar charlieegan3 avatar dependabot[bot] avatar dkorolev avatar eshepelyuk avatar gusega avatar iamleot avatar jamietanna avatar jaormx avatar jharrisonsv avatar johanfylling avatar kristiansvalland avatar mcguiresm avatar parsifal-m avatar ronnie-personal avatar sesponda avatar srenatus avatar sspaink avatar thomaskingotm avatar zregvart avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

regal's Issues

Add e2e tests

In addition to the unit tests, it would be good to add a few end-to-end tests testing the commands like regal lint with flags like --timeout, --config-file, etc.

Create reporter interface, and a "pretty" implementation

We should allow pluggable reporters of the linter results. The default, and likely the only one to include in the first release, should be a "pretty" version, that uses anything (colors, emojis..) to make the output pleasing to the eye.

We'll later need something like a JSON reporter if we want to do editor integrations or whatnot, but that can wait until later.

Conftest provides a number of reporter implementations, so could be we could use some of that for insipiration.

Lint OPA config file

A pretty common mistake in OPA is to mistype some attribute in the config, which OPA will happily swallow most of the times. It would be awesome if Regal would be able to lint OPA config files at some later point in time :)

Lint metadata annotations

It would be great if we had a linter rule to enforce correctness in metadata annotations. This rule would at minium check:

  • No extra/unknown attributes present in the metadata YAML.
  • Correct type provided for all attributes.

If we later allow for configuration of the linter, I could imagine quite a few rules to be configurable by the user, like ensuring that some attributes (title, description) are always present. But that can wait until later.

I believe we'll want some help from Go when reading metadata annotations. While we probably can do this in Rego alone, that solution is going to be quite complex, and it would be difficult to connect the metadata to the rules that they apply to... not to mention the whole metadata chain (if we want to work with that).

Reporter improvements

The reporter currently reports 1) the number of errors found and 2) the number of files in which there are errors.

A successful run results in the reporter coming back with "found 0 violations in 0 files", which makes it seem like the linter did not scan any files at all. We should report the above as we do, but add another number for the number of files scanned.

While we're at it, we should move the summary to the end of the output rather than having it at the top.

Rule: built-in function may replace custom construct

I got this idea from: https://github.com/orgs/open-policy-agent/discussions/334#discussioncomment-4845192

When new built-in functions are introduced, they sometimes "deprecate" custom built-in functions and other constructs used before them. Examples:

object_keys(o) := {k | _ = o[k[} # => object.keys
set1 & set2 == set2              # => object.subset
starts/endswith in loop          # => strings.any_prefix_match / strings.any_suffix_match
negation in loop                 # every

And I'm sure many more. Some of these will be easy, some very hard to identify correctly... but they could be tremendously useful for someone upgrading from an older version of OPA, or coming back to policy that hasn't been touched in a few years.

References in function body must allow vars declared in same body

Another issue I found today with the "no external refs" for function bodies — the rule fails on something like:

f(x) := y {
   small := lower(x)
   "abc" == small
}

Since small is not provided in args. We'd need to include all vars defined in the body in this check.

Line length

As many other linters, Regal should support warning on lines longer than X number of characters.

120 seems like a good default, but this should be configurable.

AST does not differentiate implicit/explicit = in rule head

Implicit assignment in boolean rules vs. explicit assignment using = looks the same in the parsed AST. These three constructs both render the same AST, which is perhaps not surprising given that they are "identical".

allow {
    true
}
allow = true {
    true
}
allow = true

All render the same AST for the rule:

{
    "head": {
        "name": "allow",
        "value": {"type": "boolean", "value": true},
        "ref": [{"type": "var", "value": "allow"}]
    },
    "body": [
        {
            "terms": {"type": "boolean", "value": true},
            "index": 0
        }
    ]
}

However, the second/third form should be considered a violation, as := should be used for assignment. Since we can't discern this using the AST alone, we'll need to investigate other methods, like scanning the unparsed code at that location (which should be possible with @charlieegan3's serialization fix in OPA) , extend the AST with more data, or create a custom function, or...

Collect and report metrics

In order to know how the linter performs, and if there are certain groups of rules that could be improved, it would be useful if the linter collected and reported on metrics from evaluation. This could be included in the output if some verbose setting was toggled on, or a specific flag for metrics.

Rule: TODO comment

Similar to other linters, find occurences of TODO / FIXME comments and warn when found.

Allow custom rules

Users should be able to provide their own custom rules written in Rego. We could check for these in some well-known location, like .regal/rules in the project root directory.

Relates also to #20

Consolidate input creation code

This is currently done in two places right now. There's also too much data carried in the struct as the content is duplicated in both the FileContent item and in the AST.

Allow printing the AST used for linting

Similar to how opa parse allows printing the AST of a single policy file, it would be useful to allow a regal parse command to print the entire "AST" created by regal, including all custom additions and data. This would make it easy to write and test custom rules using regular OPA.

Create docs for all rules

For each rule, we should have documentation stating:

  1. Example of the bad pattern
  2. Example of a prefered pattern
  3. Rationale + other notes
  4. Possibly links to the Styra Academy and other resources

See e.g. the hadolint rule pages for examples: https://github.com/hadolint/hadolint#rules

Docs should be included in the repository in markdown format.

VS Code integration

For later, obviously :) But it would be neat if Regal integrated with VS Code, either via the existing plugins, or new ones.

The Wasm POC @srenatus initiated would be a good place to start.

Rule: warn on != in iteration

Using != in iteration is almost always a bug:

"admin" != input.user.roles[_]

What was likely intended:

not "admin" in input.user.roles

Add to new category named "bugs".

Remediation

Testing Regal on some policy libraries found online, there are a lot of violations reported for style-related rules, like preferring := over = for top-level assignment. While we should be adamant about good style, there's also a clear risk that a user seeing hundreds of such violations either disables those rules, or worse — misses more severe violations as they drown in the amount of warnings reported.

For simple style rules like the one above, it would be interesting to explore if we could provide a remediation option, were we rewrote "bad" constructs to better ones without the user having to do the heavy lifting. Something similar to opa fmt, but limited to linter rules.

Import future keywords in evaluation?

Since we have no concerns around backwards compatibility, I think we should import the future keywords in the evaluation context, making it unnecessary to do so in each linter policy. If we do, we'll want to document this too, of course.

Rule: call to print function

While the print function is great for debugging, it's likely a mistake to leave it in before commiting. It would be good to have a check for that.

Configurable timeout for linting

It would be good to provide a --timeout flag to allow the user to decide the threshold for evaluation. We don't know yet what running regal on a huge policy library would require, but I can imagine it to take some time, so we'll likely want to go with something like 30s for a default.

Rule: `unnecessary-unification`

Avoid

input.number = 5

Prefer

input.number == 5

Motivation: while there are a few valid cases for unification, the unification operator (=) should not be used for simple comparison. TBD — more comprehensive explanation.

Provide "regal test" command for unit testing of custom rules

Since we provide custom built-in functions, and later on, custom data to help with linting, users can't rely on opa test to test their custom rules. To help with that, we should provide a regal test command, which would work similarly to opa test but with our custom additions included.

Allow multiple files and directories to be linted

The first iteration of the executable currently works with a single file only. We should improve this and allow any number of files and directories to be scanned. Running the entire set of rules against the AST of each individual Rego file is likely going to be quite expensive, and later we'll want to do some pre-processing of the files to create new datastructures more easily traversed (#13), but we can start with a naive implementation and just create an array of all the files (or rather, the AST representation of each), and scan them one-by-one (possibly in paralllell).

config: Allow rule severity of warning

A rule configured with severity "warning" should be included in the report, but should not fail the "build", i.e. not by itself change the exit code to a non-zero value.

Create simple executable embedding all rules

To start with something, we should build a simple Go application that embeds all the rules in the policy dir (excluding tests). As a dogfooding excercise, the linter could be used to lint the linter policies themselves in a GHA pipeline.

Rule length

Ignoring "complexity" for now, it would be good if Regal could at least flag rules or functions with a body exceeding X number of lines. The default could be something liberal, like 20(?), but this should be configurable.

Perphaps we should ignore lines and just count expressions.

Create table of rules in README generated from metadata

While we could do this by hand, this is likely to get out of sync with the real world quickly. It should be fairly "easy" to create a Go utility, that scans metadata of all rules in the bundle directory and creates a markdown table for display. This table should include:

  • Category
  • Rule
  • Description
  • Enabled (by default)

The rule name should be a link pointing to the Styra docs for the rule.

Unused output variable

foo {
	some x
	input[x]
}

Even in strict mode, OPA considers x to be "used", as it's referenced somewhere. Howver, unless x is referenced later in the rule (or in the rule head) it's not really used, and the above could be replaced by:

foo {
	input[_]
}

Or

foo {
	some _ in input
}

Make linter rules of strict compile time checks?

Since we'll be depending on OPA anyway, it would be useful to include all of the strictness checks from OPA but in the form of linter checks. These would naturally not be described in Rego, but would be displayed as other linter violations without requiring the use of another command (opa check, opa eval, ...) and would have links back to our documentation portal like the other linter rules do.

Allow custom configuration

We should allow providing a configuration file for enabling/disabling rules, as well as more fine-grained controls for rules where that's applicable.

I think it would make sense to use a .regal directory, where the user could provide both the configuration file, as well as custom Rego rules.

Multiple tests with same name

While OPA allows it, using multiple test rules with the same name makes it impossible to know what a test actually tests just by reading its name. Using distinct names for each test should be preferred.

Note: should ideally be checked at package level rather than file, but we currently scan one module at a time. Perhaps we could start with what we have.

Detached metadata

This came up in a discussion yesterday. Warn when # METADATA is placed anywhere but on top of packages or rules.

Rule: constant conditions

allow {
    1 == 1
    true
    false
}

And so on...

Note that we can't do much about a single true in an otherwise empty body, as:

allow := true

will be rewritten as:

allow = true {
    true
}

So we can accept that as an exception for now.

Rule: use in for membership checks

Avoid

"foo" == input.bar[_]

Prefer

"foo" in input.bar

Motivation: easier to read, makes it just as easy to invert the check using not which isn't possible using the iteration construct.

Complete rules and function should avoid returning var bound in iteration

Will the below policy work or not?

package play

f(arr) := upper(arr[_])

x := f(input.arr)

The answer depends on the number of elements in input.arr. 1 item is fine, more than that and you'll get:

policy.rego:3: eval_conflict_error: functions must not produce multiple outputs for same inputs

The big problem with this is that you can test, publish and run a policy for days or longer, before the problem manifests at runtime. The compiler accepts this, but it would be extremly helpful if Regal didn't.

Provide a JSON schema for the AST + custom additons

For both our own use, as well as for authors of custom rules, it would be really useful if we had a JSON schema defining the AST JSON, including any custom additions we have added. This would help catch mistakes in Regal policy, and could be integrated as part of our build pipeline.

Directory tree policy rules?

One thing that would be interesting (and potentially useful) is if Regal included some optional rules to check the directory structure of a policy repo, and reported on things that could be improved:

  • corresponding *_test.rego for any rego file
  • or a separate test directory
  • file names match package names
  • .editorconfig
  • "bundle mode" checking for .manifest

And so on... I'm sure we could come up with more. Low priority, but noting it down for later :)

Figure out best way to work with the AST in linter rules

For the purpose of getting started, our rules currently leverage a (perhaps too) simple model of the input, where each linter rule iterate over rules, expressions and terms looking for whatever is relevant for the rule to enforce. This however means that each rule traverses almost the entire AST. While I don't think performance is a top concern at the moment, it probaly could be later when used in larger projects. A more immediate concern however, is how the "flat" iteration model will miss some significant parts of the AST, anytime another level of nesting is encountered. There are at least two (and possibly more I haven't thought of?) types of constructs where this happens:

  • comprehensions
  • the body of an every statement

Both of these create a new nested context, which in turn of course could contain even more nested comrehensions or bodies. While I don't think we'll need to worry too much about supporting "infinte" levels of nesting — as anything more than 2-3 levels would arguably be a code style violation itself, we'll definitely will want the linter to cover this code! How? Without recursion, this is non-trivial. Some options I can think of:

  1. Each rule use something like the walk builtin to traverse the AST — even more expensive.
  2. Have each rule declare its "dependencies" — i.e. the type of constructs that the rule applies to, like "function calls", "expressions", "imports" and so on... and Regal would prepare a "view" of the input based on that.
  3. Have regal run an "indexing" step before executing any rules, where the AST is traversed and groups are created for each type of construct, which the rules could then make use of.
  4. Maybe have Go code (via a custom builtin function or whatnot) provide a way to "flatten" nested constructs, or by other means allow linter rules to keep with the "simple" model, but have a tool to help with nesting. I'm not sure what that would look like though.

I think option 3 is appealing, as it would allow rules to deal only with the constructs that are relevant to them, and without having to know much about the context in which they're found. But we'd need to give this some more thought.

Allow enforcement of custom naming conventions

While we'll want to enforce snake_case naming of rules and vars by default, it'd be nice if we provided an optional rule to enforce custom naming conventions. This could be provided in the form of a regex, that would be checked against all rule names and vars.

Rule: unification used for variable assignment

Avoid

x = 5

Prefer

x := 5

Motivation: while there are a few valid cases for unification, the unification operator (=) should not be used for simple assignment. TBD — more comprehensive explanation.

Rules for common organizational requirements

I noticed this today: open-policy-agent/opa#2598

And while it could potentially be done in OPA, this seems like an excellent use-case for Regal. The example rules mentioned in the ticket:

  • App repos should not be able to modify the system package except for the system/log/mask decision
  • App policy packages must be namespaced under package acmecorp.<app_name>
  • App API authorization policies must include a default allow = false rule (any other value is not allowed for the default allow rule)

All seem like they would be quite easy to add as optional, configurable rules. If we want to leave it outside of Regal core, I could see how we could provide these type of rules in an external bundle... but having them packaged would be convenient.

Unused return value

Calling a built-in function returning a non-boolean value without actualy assigning or using that value is likely a mistake.

Not sure about rules, but perhaps we'd want to check those too 🤔

Detect expressions that may be moved outside of loop

I've seen a few policies where expressions are evaluated in each iteration of a loop even though they don't make use of values bound in the loop, i.e.

allow {
    some user in data.users
    endswith(input.email, "acmecorp.com") # this should be moved out of the loop
    # more things here
}

Warning on this would be great.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.