Skip to content

Commit

Permalink
Use rego.v1 and introduce Regal
Browse files Browse the repository at this point in the history
Signed-off-by: Anders Eknert <[email protected]>
  • Loading branch information
anderseknert committed Feb 20, 2024
1 parent 83e25e8 commit 0c782ab
Show file tree
Hide file tree
Showing 32 changed files with 247 additions and 207 deletions.
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
20 changes: 20 additions & 0 deletions .github/workflows/opa-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,23 @@ jobs:

- name: OPA integration tests
run: test/integration.sh

regal-lint:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4

- name: Setup OPA
uses: open-policy-agent/setup-opa@v2

- name: OPA check strict
run: opa check --strict .

- name: Setup Regal
uses: StyraInc/setup-regal@v1
with:
version: latest

- name: Regal Lint
run: regal lint --format github .
20 changes: 20 additions & 0 deletions .regal/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
rules:
custom:
one-liner-rule:
level: error
imports:
prefer-package-imports:
level: error
ignore:
files:
- "*_test.rego"
style:
line-length:
level: error
non-breakable-word-threshold: 80
testing:
print-or-trace-call:
level: error
ignore:
files:
- "assertions.rego"
12 changes: 7 additions & 5 deletions ci/resourcetypes.rego
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package aws.cloudformation

import future.keywords
import rego.v1

# METADATA
# description: |
# Get all resource types from the single-file JSON schema provided by AWS for the us-west-1
# region. See https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-resource-specification.html
# for a list of all available files in all regions
resource_types[type] {
some type, _ in http.send({
resource_types contains type if {
resp := http.send({
"method": "GET",
"url": "https://d68hl49wbnanq.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json",
}).body.ResourceTypes
})

some type, _ in resp.body.ResourceTypes
}

# METADATA
# description: |
# Transform all resource types into a their wildcard representation, i.e. "target".
# Example: AWS::S3:Bucket => AWS::S3::*
resource_targets[target] {
resource_targets contains target if {
some resource_type in resource_types
target := concat("::", array.concat(array.slice(split(resource_type, "::"), 0, 2), ["*"]))
}
Expand Down
44 changes: 15 additions & 29 deletions examples/policy/assertions.rego
Original file line number Diff line number Diff line change
Expand Up @@ -5,74 +5,60 @@
#
package assertions

import future.keywords.in
import rego.v1

# METADATA
# description: Assert expected is equal to result, or fail while printing both inputs to console
assert_equals(expected, result) {
expected == result
}
assert_equals(expected, result) if expected == result

assert_equals(expected, result) = false {
assert_equals(expected, result) := false if {
expected != result
print("expected equals:", _quote_if_string(expected), "got:", result)
}

# METADATA
# description: Assert expected is not equal to result, or fail while printing both inputs to console
assert_not_equals(expected, result) {
expected != result
}
assert_not_equals(expected, result) if expected != result

assert_not_equals(expected, result) = false {
assert_not_equals(expected, result) := false if {
expected == result
print("expected not equals:", _quote_if_string(expected), "got:", result)
}

# METADATA
# description: Assert item is in coll, or fail while printing the collection to console
assert_in(item, coll) {
item in coll
}
assert_in(item, coll) if item in coll

assert_in(item, coll) = false {
assert_in(item, coll) := false if {
not item in coll
print("expected", type_name(item), _quote_if_string(item), "in", type_name(coll), "got:", coll)
}

# METADATA
# description: Assert item is not in coll, or fail while printing the collection to console
assert_not_in(item, coll) {
not item in coll
}
assert_not_in(item, coll) if not item in coll

assert_not_in(item, coll) = false {
assert_not_in(item, coll) := false if {
item in coll
print("expected", type_name(item), _quote_if_string(item), "not in", type_name(coll), "got:", coll)
}

# METADATA
# description: Assert provided collection is empty, or fail while printing the collection to console
assert_empty(coll) {
count(coll) == 0
}
assert_empty(coll) if count(coll) == 0

assert_empty(coll) = false {
assert_empty(coll) := false if {
count(coll) != 0
print("expected empty", type_name(coll), "got:", coll)
}

# METADATA
# description: Fail with provided message
fail(msg) {
fail(msg) if {
print(msg)
false
false # regal ignore:constant-condition
}

_quote_if_string(x) = concat("", [`"`, x, `"`]) {
is_string(x)
}
_quote_if_string(x) := concat("", [`"`, x, `"`]) if is_string(x)

_quote_if_string(x) = x {
not is_string(x)
}
_quote_if_string(x) := x if not is_string(x)
8 changes: 4 additions & 4 deletions examples/policy/authz.rego
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
#
package system.authz

default allow = false
import rego.v1

default allow := false

# METADATA
# description: |
# See the README.md file contained in this repo for how to configure an AWS Secret to
# use as a token for client connections.
#
allow {
input.identity == "changeme"
}
allow if input.identity == "changeme"
2 changes: 2 additions & 0 deletions examples/policy/aws.rego
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
# organizations:
# - Styra
package aws

import rego.v1
16 changes: 11 additions & 5 deletions examples/policy/ec2/security_group/security_group.rego
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package aws.ec2.securitygroup

import future.keywords
import rego.v1

deny[msg] {
deny contains msg if {
input.resource.properties.SecurityGroupIngress[0].CidrIp == "0.0.0.0/0"

msg := sprintf("Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): %s", [input.resource.id])
msg := sprintf(
"Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): %s",
[input.resource.id],
)
}

deny[msg] {
deny contains msg if {
input.resource.properties.SecurityGroupIngress[0].CidrIpv6 == "::/0"

msg := sprintf("Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): %s", [input.resource.id])
msg := sprintf(
"Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): %s",
[input.resource.id],
)
}
6 changes: 3 additions & 3 deletions examples/policy/ec2/security_group/security_group_test.rego
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package aws.ec2.securitygroup_test

import future.keywords
import rego.v1

import data.aws.ec2.securitygroup.deny

import data.assertions.assert_empty

import data.test_helpers.create_with_properties

test_deny_if_security_group_allows_all_destinations {
test_deny_if_security_group_allows_all_destinations if {
inp := create_with_properties("AWS::EC2::SecurityGroup", "SecurityGroup", {"SecurityGroupIngress": [{
"CidrIp": "0.0.0.0/0",
"IpProtocol": "-1",
Expand All @@ -17,7 +17,7 @@ test_deny_if_security_group_allows_all_destinations {
deny["Security Group cannot contain rules allow all destinations (0.0.0.0/0 or ::/0): SecurityGroup"] with input as inp
}

test_allow_if_security_group_cidr_is_set {
test_allow_if_security_group_cidr_is_set if {
inp := create_with_properties("AWS::EC2::SecurityGroup", "SecurityGroup", {"SecurityGroupIngress": [{
"CidrIp": "10.0.0.0/16",
"IpProtocol": "-1",
Expand Down
8 changes: 3 additions & 5 deletions examples/policy/eks/cluster/logging_enabled.rego
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package aws.eks.cluster

import future.keywords
import rego.v1

deny[msg] {
deny contains msg if {
not cluster_logging_enabled
msg := sprintf("no logging types are enabled for cluster: %s", [input.resource.id])
}

cluster_logging_enabled {
count(input.resource.properties.Logging.ClusterLogging.EnabledTypes) > 0
}
cluster_logging_enabled if count(input.resource.properties.Logging.ClusterLogging.EnabledTypes) > 0
6 changes: 3 additions & 3 deletions examples/policy/eks/cluster/logging_enabled_test.rego
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package aws.eks.logging_enabled_test

import future.keywords
import rego.v1

import data.aws.eks.cluster.deny

Expand All @@ -9,7 +9,7 @@ import data.assertions.assert_not_in

import data.test_helpers.create_with_properties

test_allow_cluster_logging_enabled {
test_allow_cluster_logging_enabled if {
inp := create_with_properties("AWS::EKS::Cluster", "EksCluster", {
"ResourcesVpcConfig": {
"RoleArn": "<MY_EKS_SERVICE_ROLE_ARN>",
Expand All @@ -25,7 +25,7 @@ test_allow_cluster_logging_enabled {
assert_not_in(msg, deny) with input as inp
}

test_deny_no_logging_configuration {
test_deny_no_logging_configuration if {
inp := create_with_properties("AWS::EKS::Cluster", "EksCluster", {
"ResourcesVpcConfig": {
"RoleArn": "<MY_EKS_SERVICE_ROLE_ARN>",
Expand Down
14 changes: 5 additions & 9 deletions examples/policy/eks/cluster/public_api.rego
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package aws.eks.cluster

import future.keywords
import rego.v1

deny[msg] {
deny contains msg if {
not public_endpoint_disabled
msg := sprintf("public endpoint needs to be disabled for cluster: %s", [input.resource.id])
}

deny[msg] {
deny contains msg if {
public_endpoint_disabled
not private_endpoint_enabled
msg := sprintf("invalid configuration, please enable private api for cluster: %s", [input.resource.id])
}

public_endpoint_disabled {
input.resource.properties.ResourcesVpcConfig.EndpointPublicAccess == "false"
}
public_endpoint_disabled if input.resource.properties.ResourcesVpcConfig.EndpointPublicAccess == "false"

private_endpoint_enabled {
input.resource.properties.ResourcesVpcConfig.EndpointPrivateAccess == "true"
}
private_endpoint_enabled if input.resource.properties.ResourcesVpcConfig.EndpointPrivateAccess == "true"
10 changes: 5 additions & 5 deletions examples/policy/eks/cluster/public_api_test.rego
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package aws.eks.public_api_test

import data.aws.eks.cluster.deny
import rego.v1

import future.keywords
import data.aws.eks.cluster.deny

import data.assertions.assert_in
import data.assertions.assert_not_in

import data.test_helpers.create_with_properties

test_allow_cluster_private_api {
test_allow_cluster_private_api if {
inp := create_with_properties("AWS::EKS::Cluster", "EksCluster", {"ResourcesVpcConfig": {
"RoleArn": "<MY_EKS_SERVICE_ROLE_ARN>",
"SubnetIds": ["<MY_SUBNET_ID>"],
Expand All @@ -21,7 +21,7 @@ test_allow_cluster_private_api {
assert_not_in(msg, deny) with input as inp
}

test_deny_cluster_public_api {
test_deny_cluster_public_api if {
inp := create_with_properties("AWS::EKS::Cluster", "EksCluster", {"ResourcesVpcConfig": {
"RoleArn": "<MY_EKS_SERVICE_ROLE_ARN>",
"SubnetIds": ["<MY_SUBNET_ID>"],
Expand All @@ -33,7 +33,7 @@ test_deny_cluster_public_api {
assert_in(msg, deny) with input as inp
}

test_deny_cluster_no_access {
test_deny_cluster_no_access if {
inp := create_with_properties("AWS::EKS::Cluster", "EksCluster", {"ResourcesVpcConfig": {
"RoleArn": "<MY_EKS_SERVICE_ROLE_ARN>",
"SubnetIds": ["<MY_SUBNET_ID>"],
Expand Down
Loading

0 comments on commit 0c782ab

Please sign in to comment.