Skip to content

Commit

Permalink
Add Regal for linting
Browse files Browse the repository at this point in the history
Hi there Spacelift friends πŸ‘‹πŸ™‚ This PR introduces [Regal](https://github.com/styrainc/regal)
(as I know some of you are familiar with from before!) for linting the Rego found in this repo.

A few issues reported have been fixed as part of the PR:

* [custom-has-key-construct](https://docs.styra.com/regal/rules/idiomatic/custom-has-key-construct)
* [custom-in-construct](https://docs.styra.com/regal/rules/idiomatic/custom-in-construct)
* [prefer-set-or-object-rule](https://docs.styra.com/regal/rules/idiomatic/prefer-set-or-object-rule)
* [non-raw-regex-pattern](https://docs.styra.com/regal/rules/idiomatic/non-raw-regex-pattern)
* [unconditional-assignment](https://docs.styra.com/regal/rules/style/unconditional-assignment)
* [use-assignment-operator](https://docs.styra.com/regal/rules/style/use-assignment-operator)
* [use-in-operator](https://docs.styra.com/regal/rules/idiomatic/use-in-operator)
* [use-some-for-output-vars](https://docs.styra.com/regal/rules/idiomatic/use-some-for-output-vars)

A few rules have been ignored for now using the provided `.regal/config.yaml` file. Some
of those would be easy to fix later, but I'll leave that decision to you.

In addition to the Regal rules, I've also made sure the project "builds" using OPA
[strict mode](https://www.openpolicyagent.org/docs/latest/policy-language/#strict-mode)
(`opa check --strict`). The violations related to that have also been fixed fixed:

* Assignment shadowing `input`
* Unused assignment

Finally, I've added a Regal step to the project's CI workflow, so that new code is
linted as part of that process. I've also removed the formatter check job, as that
is included in Regal (the [opa-fmt](https://docs.styra.com/regal/rules/style/opa-fmt) rule).

Signed-off-by: Anders Eknert <[email protected]>
  • Loading branch information
anderseknert committed Nov 9, 2023
1 parent 00835cd commit 9779ba5
Show file tree
Hide file tree
Showing 70 changed files with 264 additions and 284 deletions.
25 changes: 8 additions & 17 deletions .github/workflows/continuous-integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,17 @@ on:
pull_request:

jobs:
formatting-check:
name: Formatting Check
regal-check:
name: Regal Check
runs-on: ubuntu-latest

steps:
- name: Check out
uses: actions/checkout@v3

- name: Install OPA
uses: open-policy-agent/setup-opa@v2
- uses: actions/checkout@v3
- name: Setup Regal
uses: StyraInc/[email protected]
with:
version: 0.51.0

- name: Check formatting
run: |
output="$(opa fmt --diff .)"
if [ -n "$output" ]; then
echo "$output"
exit 1
fi
version: v0.11.0
- run: regal lint --format=github .

syntax-check:
name: Syntax Check
Expand All @@ -46,7 +37,7 @@ jobs:
# KLUDGE: plan/check-sanitized-value.rego needs to be ignored because it uses the custom sanitized() function
policies=$(find . -type f -regex '.*\.rego$' | grep -v _test.rego | grep -v plan/check-sanitized-value.rego)
for policy in $policies; do
opa check $policy
opa check --strict $policy
done
unit-tests:
Expand Down
22 changes: 22 additions & 0 deletions .regal/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
rules:
idiomatic:
no-defined-entrypoint:
level: ignore
style:
external-reference:
level: ignore
line-length:
level: ignore
prefer-some-in-iteration:
level: ignore
todo-comment:
level: ignore
testing:
test-outside-test-package:
level: ignore

capabilities:
from:
# Feel free to submit a PR to have "spacelift" added as an engine!
engine: opa
version: v0.51.0
4 changes: 3 additions & 1 deletion access/downgrade-access-after-hours.rego
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package spacelift

import future.keywords.in

# When things go wrong it's usually because someone did something, like an infra deployment.
# Let's try to make sure they're in the office when doing so and restrict write access to business hours
# and office IP range.
Expand All @@ -17,7 +19,7 @@ weekday := time.weekday(now)
ip := input.request.remote_ip

write {
input.session.teams[_] == "Product team"
"Product team" in input.session.teams
}

deny_write {
Expand Down
6 changes: 4 additions & 2 deletions access/engineering-team-access.rego
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package spacelift

import future.keywords.in

# This example access policy gives everyone in the "Engineering" GitHub team
# read access to the stack.
#
# You can read more about access policies here:
# https://docs.spacelift.io/concepts/policy/stack-access-policy

read {
input.session.teams[_] == "Engineering"
"Engineering" in input.session.teams
}

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
8 changes: 4 additions & 4 deletions access/label-based-team-access.rego
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ trim_whitespace(s) := concat("-", parts) if {
}

# Convert all Teams to the ID format
teams := {x | x := trim_whitespace(input.session.teams[_])}
teams[trim_whitespace(input.session.teams[_])]

# Find access pattern matching labels and return just "access:team" for each
labels := {trim_prefix(label, "access:") |
labels[trim_prefix(label, "access:")] {
some label in input.stack.labels
not label == ""
regex.match("^access:(read|write|deny):[^:]+$", label)
regex.match(`^access:(read|write|deny):[^:]+$`, label)
}

# check if any team would allow request "access" level
Expand All @@ -42,4 +42,4 @@ read := test("read")
deny_write if input.stack.administrative

# Turn on input sampling for all attempts
sample = true
sample := true
8 changes: 4 additions & 4 deletions access/protect-administrative-stacks.rego
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package spacelift

# Administrative stacks are powerful - getting write access to one is almost
# as good as being an admin - you can define and attach contexts and policies.
# So let's deny write access to them entirely. This works since access policies
# Administrative stacks are powerful - getting write access to one is almost
# as good as being an admin - you can define and attach contexts and policies.
# So let's deny write access to them entirely. This works since access policies
# are not evaluated for admin users.

deny_write {
Expand All @@ -11,4 +11,4 @@ deny_write {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
4 changes: 2 additions & 2 deletions access/slack-channel-access.rego
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package spacelift
# attach this policy to a given stack, and this would provide
# the Slack Channel "dev-notifications" access to your Spacelift
# stack's data.
#
#
# NOTE: If you are looking to scope access to individual Slack channels
# you should consider using the channel id, rather than the name, as names can change.
write {
Expand All @@ -13,4 +13,4 @@ write {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
14 changes: 8 additions & 6 deletions access/who-when-where-access-restrictions.rego
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package spacelift

import future.keywords.in

# In case things go wrong, we want you to be there.
#
# You know when things go wrong it's usually because someone did something.
# Like an infra deployment. Let's try to make sure they're in the office
# when doing so and restrict write access to business hours and office IP range.
# You know when things go wrong it's usually because someone did something.
# Like an infra deployment. Let's try to make sure they're in the office
# when doing so and restrict write access to business hours and office IP range.
# This policy is best combined with one that gives read access.

# Let's set some variables we can reference later
Expand All @@ -20,10 +22,10 @@ ip := input.request.remote_ip

# Now let's define use those above variables to define the criteria
# for who can have write access, when, and from where (the ip)
#
#
# Allow write access from the Product team
write {
input.session.teams[_] == "Product team"
"Product team" in input.session.teams
}

# Only allow access during weekdays
Expand All @@ -47,4 +49,4 @@ deny_write {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
2 changes: 1 addition & 1 deletion approval/allowlist-task-commands.rego
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ approve {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
2 changes: 1 addition & 1 deletion approval/approval-outside-working-hours.rego
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,4 @@ reject {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
21 changes: 7 additions & 14 deletions approval/approval-outside-working-hours_test.rego
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,56 @@ mock_created_at_weekend := 1692477510000000000

# Test automatic approval for a weekday between 9am to 5pm
test_automatic_approve_weekday_morning {
input := {
approve with input as {
"run": {"created_at": mock_created_at_weekday_morning},
"reviews": {"current": {"approvals": [], "rejections": []}},
}
approve with input as input
}

# Test required approval for a weekday after 5pm
test_required_approval_weekday_evening {
input := {
approve with input as {
"run": {"created_at": mock_created_at_weekday_evening},
"reviews": {"current": {"approvals": ["user1"], "rejections": []}},
}
approve with input as input
}

# Test missing approval for a weekday after 5pm
test_missing_approval_weekday_evening {
input := {
not approve with input as {
"run": {"created_at": mock_created_at_weekday_evening},
"reviews": {"current": {"approvals": [], "rejections": []}},
}
not approve with input as input
}

# Test required approval during weekend
test_required_approval_weekend {
input := {
approve with input as {
"run": {"created_at": mock_created_at_weekend},
"reviews": {"current": {"approvals": ["user1"], "rejections": []}},
}
approve with input as input
}

# Test missing approval during weekend
test_missing_approval_weekend {
input := {
not approve with input as {
"run": {"created_at": mock_created_at_weekend},
"reviews": {"current": {"approvals": [], "rejections": []}},
}
not approve with input as input
}

# Test reject due to a rejection review
test_rejection_due_to_review {
input := {
reject with input as {
"run": {"created_at": mock_created_at_weekend},
"reviews": {"current": {"approvals": ["user1"], "rejections": ["user2"]}},
}
reject with input as input
}

# Test no rejection when there's no rejection review
test_no_rejection_when_no_review {
input := {
not reject with input as {
"run": {"created_at": mock_created_at_weekend},
"reviews": {"current": {"approvals": ["user1"], "rejections": []}},
}
not reject with input as input
}
8 changes: 5 additions & 3 deletions approval/require-approval-from-security-team.rego
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package spacelift

import future.keywords.in

# This policy approves any runs when someone from Security team approves the changes to the resources in the list,
# and rejects any runs when someone from other teams tries to approve the changes.

Expand All @@ -10,7 +12,7 @@ approve {
input.run.state != "UNCONFIRMED"
}

approval_list = [
approval_list := [
"aws_iam_access_key",
"aws_security_group",
"aws_security_group_rule",
Expand Down Expand Up @@ -54,7 +56,7 @@ approvals := input.reviews.current.approvals

# Let's define what it means to be approved by Security team.
security_approval {
approvals[_].session.teams[_] == "Security"
"Security" in approvals[_].session.teams
}

# Approve when Security team approve and Require at least 1 approval:
Expand All @@ -70,4 +72,4 @@ reject {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
2 changes: 1 addition & 1 deletion approval/require-private-worker.rego
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ reject {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
10 changes: 6 additions & 4 deletions approval/role-based-approval.rego
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package spacelift

import future.keywords.in

# First, let's define all conditions that require explicit
# user approval.
requires_approval {
Expand All @@ -18,15 +20,15 @@ approvals := input.reviews.current.approvals

# Let's define what it means to be approved by a director, DevOps amd Security.
director_approval {
approvals[_].session.teams[_] == "Director"
"Director" in approvals[_].session.teams
}

devops_approval {
approvals[_].session.teams[_] == "DevOps"
"DevOps" in approvals[_].session.teams
}

security_approval {
approvals[_].session.teams[_] == "Security"
"Security" in approvals[_].session.teams
}

# Approve when a single director approves:
Expand All @@ -42,4 +44,4 @@ approve {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
2 changes: 1 addition & 1 deletion approval/task-and-run-approvals.rego
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ approve {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
8 changes: 4 additions & 4 deletions approval/two-approvals-no-rejections.rego
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package spacelift

# In this example, each Unconfirmed run will require two approvals -
# including proposed runs triggered by Git events. Additionally,
# the run should have no rejections. Anyone who rejects the run will
# In this example, each Unconfirmed run will require two approvals -
# including proposed runs triggered by Git events. Additionally,
# the run should have no rejections. Anyone who rejects the run will
# need to change their mind in order for the run to go through.

approve {
Expand All @@ -16,4 +16,4 @@ approve {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
2 changes: 1 addition & 1 deletion approval/two-approvals-two-rejections.rego
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ reject {

# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample = true
sample := true
Loading

0 comments on commit 9779ba5

Please sign in to comment.