Skip to content

Commit

Permalink
feat: add examples of custom checks (#295)
Browse files Browse the repository at this point in the history
      * feat: add examples of custom checks

Signed-off-by: Nikita Pivkin <[email protected]>

* test: add tests for examples

Signed-off-by: Nikita Pivkin <[email protected]>

---------

Signed-off-by: Nikita Pivkin <[email protected]>
  • Loading branch information
nikpivkin authored Nov 30, 2024
1 parent d0839ca commit ebd9a00
Show file tree
Hide file tree
Showing 29 changed files with 3,059 additions and 15 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ rego: fmt-rego test-rego

.PHONY: fmt-rego
fmt-rego:
opa fmt -w lib/ checks/
opa fmt -w lib/ checks/ examples/

.PHONY: test-rego
test-rego:
go run ./cmd/opa test --explain=fails lib/ checks/ --ignore '*.yaml'
go run ./cmd/opa test --explain=fails lib/ checks/ examples/ --ignore '*.yaml'

.PHONY: bundle
bundle: create-bundle verify-bundle
Expand Down
3 changes: 3 additions & 0 deletions examples/cloudformation/data/tags.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
required_tags:
- Environment
- Owner
48 changes: 48 additions & 0 deletions examples/cloudformation/ensure_required_tags.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# METADATA
# title: Ensure resources have required tags
# description: |
# Ensure that all resources in the CloudFormation template have the required tags such as "Environment" and "Owner".
# These tags help in resource tracking, management, and categorization, making it easier to automate processes
# and manage AWS infrastructure.
# scope: package
# related_resources:
# - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/working-with-templates.html
# custom:
# id: USR-CF-0001
# avd_id: USR-CF-0001
# severity: MEDIUM
# short_code: ensure-required-tags
# recommended_action: Ensure all resources are tagged with the appropriate metadata tags to facilitate resource management.
# input:
# selector:
# - type: json
package user.cf.ensure_required_tags

import data.required_tags

import rego.v1

deny contains res if {
some resource in input.Resources
not resource.Tags
res := result.new(
sprintf("Resource $q does not have required tags %v", [resource.Type, required_tags]),
{},
)
}

deny contains res if {
some resource in input.Resources
some required_tag in required_tags
not has_required_tag(resource.Tags, required_tag)
res := result.new(
sprintf("Resource %q does not have the required %q tag", [resource.Type, required_tag]),
{},
)
}

# Helper function to check if the tag exists
has_required_tag(tags, tag_name) if {
some tag in tags
tag.Key == tag_name
}
80 changes: 80 additions & 0 deletions examples/cloudformation/ensure_required_tags_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package user.cf.ensure_required_tags_test

import rego.v1

import data.user.cf.ensure_required_tags as check

test_deny_resources_without_tags if {
inp := {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {"MyEC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": "t2.micro",
"ImageId": "ami-0abcdef1234567890",
},
}},
}

res := check.deny with input as inp with data.required_tags as {
"Environment",
"Owner",
}

count(res) == 1
}

test_deny_resources_without_required_tags if {
inp := {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {"MyEC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": "t2.micro",
"ImageId": "ami-0abcdef1234567890",
},
"Tags": [
{
"Key": "Foo",
"Value": "foo",
},
{
"Key": "Bar",
"Value": "bar",
},
],
}},
}

res := check.deny with input as inp with data.required_tags as {
"Environment",
"Owner",
}
count(res) == 2
}

test_allow_resources_with_required_tags if {
inp := {
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {"MyEC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": "t2.micro",
"ImageId": "ami-0abcdef1234567890",
},
"Tags": [
{
"Key": "Environment",
"Value": "Production",
},
{
"Key": "Owner",
"Value": "JohnDoe",
},
],
}},
}

res := check.deny with input as inp
res == set()
}
12 changes: 12 additions & 0 deletions examples/cloudformation/template.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"MyEC2Instance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": "t2.micro",
"ImageId": "ami-0abcdef1234567890"
}
}
}
}
9 changes: 9 additions & 0 deletions examples/docker-compose/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: '3.8'

services:
web:
build: .
ports:
- "8000:5000"
redis:
image: "redis:latest"
30 changes: 30 additions & 0 deletions examples/docker-compose/latest_tag.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# METADATA
# title: Avoid using 'latest' tag in container images
# description: |
# The 'latest' tag in container images does not guarantee consistency across deployments.
# Using explicit version tags ensures that the exact image version is used,
# reducing the risk of unexpected changes or vulnerabilities in your environment.
#
# Avoid using 'latest' in your `docker-compose.yaml` files to maintain predictable deployments.
# scope: package
# related_resources:
# - https://docs.docker.com/reference/compose-file/services/#image
# custom:
# id: USR-COMPOSE-0001
# avd_id: USR-COMPOSE-0001
# provider: generic
# severity: MEDIUM
# short_code: avoid-latest-tag
# recommended_action: Use specific image tags instead of 'latest' for reliable deployments.
# input:
# selector:
# - type: yaml
package user.compose.latest_tag

import rego.v1

deny contains res if {
some name, service in input.services
endswith(service.image, ":latest")
res := result.new(sprintf("Avoid using 'latest' tag in container image for %q", [name]), {})
}
31 changes: 31 additions & 0 deletions examples/docker-compose/latest_tag_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package user.compose.latest_tag_test

import rego.v1

import data.user.compose.latest_tag as check

test_deny_latest_tag if {
inp := {"services": {
"web": {
"build": ".",
"ports": ["8000:5000"],
},
"redis": {"image": "redis:latest"},
}}

res := check.deny with input as inp
count(res) == 1
}

test_allow_specific_tag if {
inp := {"services": {
"web": {
"build": ".",
"ports": ["8000:5000"],
},
"redis": {"image": "redis:7.4"},
}}

res := check.deny with input as inp
res == set()
}
5 changes: 5 additions & 0 deletions examples/dockerfile/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM ubuntu:20.04

RUN apt-get update && apt-get install curl

CMD ["bash"]
50 changes: 50 additions & 0 deletions examples/dockerfile/avoid_unstable_packages.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# METADATA
# title: Avoid installing packages without specific versions
# description: |
# Installing packages without specifying the version can lead to instability or unexpected behavior.
# Always specify the exact version of the package to ensure predictable builds and avoid pulling in unintended updates.
# schemas:
# - input: schema["dockerfile"]
# related_resources:
# - https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#apt-get
# custom:
# id: USR-DF-0001
# avd_id: USR-DF-0001
# severity: HIGH
# short_code: avoid-unstable-packages
# recommended_action: Specify the exact version of packages when using RUN instructions.
# input:
# selector:
# - type: dockerfile
package user.dockerfile.avoid_unstable_packages

import rego.v1

deny contains res if {
some stage in input.Stages
some instruction in stage.Commands

instruction.Cmd == "run"

some val in instruction.Value

# custom function
cmds := sh.parse_commands(val)

some cmd in cmds
cmd[0] == "apt-get"
cmd[1] == "install"

args := array.slice(cmd, 2, count(cmd))

some arg in args

# skip flags
not startswith(arg, "-")
not contains(arg, "=")

res := result.new(
sprintf("Avoid installing package %q without specifying a version.", [arg]),
{},
)
}
43 changes: 43 additions & 0 deletions examples/dockerfile/avoid_unstable_packages_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package user.dockerfile.avoid_unstable_packages_test

import rego.v1

import data.user.dockerfile.avoid_unstable_packages as check

test_allow_package_with_pinned_version if {
inp := {"Stages": [{
"Name": "ubuntu",
"Commands": [
{
"Cmd": "run",
"Value": ["apt-get update && apt-get install curl=7.68.0-1ubuntu2.6"],
},
{
"Cmd": "cmd",
"Value": ["bash"],
},
],
}]}

res := check.deny with input as inp
res == set()
}

test_deny_package_without_version if {
inp := {"Stages": [{
"Name": "ubuntu",
"Commands": [
{
"Cmd": "run",
"Value": ["apt-get update && apt-get install curl"],
},
{
"Cmd": "cmd",
"Value": ["bash"],
},
],
}]}

res := check.deny with input as inp
count(res) = 1
}
4 changes: 4 additions & 0 deletions examples/ignore.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package trivy

# disable all built-in checks
ignore := startswith(input.AVDID, "AVD-")
19 changes: 19 additions & 0 deletions examples/kubernetes/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app-container
image: web-app:1.0
ports:
- containerPort: 80
32 changes: 32 additions & 0 deletions examples/kubernetes/no_deployment_allowed.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# METADATA
# title: Deployment not allowed
# description: |
# This check ensures that Kubernetes Deployments are not used in your environment.
# Deployments may be restricted for various reasons, such as the need to use other controllers (e.g., StatefulSets, DaemonSets) or the avoidance of certain deployment strategies.
#
# Avoid using the 'Deployment' kind to ensure compliance with your organization's deployment strategy.
# scope: package
# schemas:
# - input: schema["kubernetes"]
# related_resources:
# - https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
# custom:
# id: USR-KUBE-0001
# avd_id: USR-KUBE-0001
# severity: HIGH
# short_code: no-deployment-allowed
# recommended_action: Avoid using Kubernetes Deployments. Consider alternative resources like StatefulSets or DaemonSets.
# input:
# selector:
# - type: kubernetes
package user.kubernetes.no_deployment_allowed

import rego.v1

deny contains res if {
input.kind == "Deployment"
res := result.new(
sprintf("Found deployment '%s' but deployments are not allowed", [input.metadata.name]),
input.metadata,
)
}
Loading

0 comments on commit ebd9a00

Please sign in to comment.