Skip to content
This repository has been archived by the owner on Sep 3, 2024. It is now read-only.

Commit

Permalink
Cloudformation Support (#84)
Browse files Browse the repository at this point in the history
* Support for CloudFormation

* [RM-5219] Skip cloudtrail_s3_data_logging rules in templates without trails (#78)

* [RM-5229] Docker image & bin/regula fixes

* [RM-5269] Standardization (#80)

* [RM-5269] Standardize package structure

* [RM-5269] Standardize test package names and structure

* [RM-5269] Fix regula report test import

* [RM-5269] Re-organize cfn/aws -> cfn

* [RM-5269] Update check-naming script and fix violations

* Update README.md (#81)

* Add git to Dockerfile for terraform module support (#83)

* Update changelog for v0.6.0 release

Co-authored-by: Jasper Van der Jeugt <[email protected]>
Co-authored-by: fugue-chris <[email protected]>
  • Loading branch information
3 people authored Mar 18, 2021
1 parent 1858f03 commit bdf29b6
Show file tree
Hide file tree
Showing 323 changed files with 13,948 additions and 870 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.cfn linguist-language=YAML
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.terraform/
.DS_Store
venv/
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# CHANGELOG
- 0.6.0 (2021-03-18)
* Add support for CloudFormation templates.
* Add 23 new CIS AWS rules for CloudFormation templates.
* Reorganize rules and tests and standardize rule names.
* Update control and compliance family names to new format.

- 0.5.0 (2020-08-21)
* New rule: Ensure AWS S3 Buckets are encrypted.
Expand Down
31 changes: 31 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
FROM python:3.9.2-alpine3.13

# We need bash for the main regula script since it uses arrays.
# We need git to support terraform modules
RUN apk add --update bash git && rm -rf /var/cache/apk/*

# Install OPA.
ARG OPA_VERSION=0.26.0
RUN wget -O '/usr/local/bin/opa' \
"https://github.com/open-policy-agent/opa/releases/download/v${OPA_VERSION}/opa_linux_amd64" &&\
chmod +x '/usr/local/bin/opa'

# Install terraform.
ARG TERRAFORM_VERSION=0.14.7
ENV TF_IN_AUTOMATION=true
RUN wget -O "/tmp/terraform-${TERRAFORM_VERSION}.zip" \
"https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" && \
unzip -d '/usr/local/bin' "/tmp/terraform-${TERRAFORM_VERSION}.zip" &&\
rm "/tmp/terraform-${TERRAFORM_VERSION}.zip"

# Install cfn-flip
ARG CFNFLIP_VERSION=1.2.3
RUN pip install "cfn-flip==${CFNFLIP_VERSION}"

# Update regula files
RUN mkdir -p /opt/regula
COPY lib /opt/regula/lib
COPY rules /opt/regula/rules
COPY bin/regula /usr/local/bin

ENTRYPOINT ["regula", "-", "/opt/regula"]
200 changes: 100 additions & 100 deletions README.md

Large diffs are not rendered by default.

82 changes: 52 additions & 30 deletions bin/regula
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,47 @@ set -o nounset -o errexit -o pipefail

# Basic command line argument handling.
if [[ $# -lt 1 ]]; then
1>&2 echo "Usage: $0 [TERRAFORM_DIR or TERRAFORM_PLAN_JSON] [REGO_PATHS..]"
1>&2 echo "Usage: $0 [INPUT_PATH] [REGO_PATHS..]"
1>&2 echo ""
1>&2 echo "INPUT_PATH can be a terraform directory, a terraform plan in"
1>&2 echo "JSON format or a cloudformation template."
1>&2 echo ""
1>&2 echo "Regula is a little wrapper to run Rego validations on terraform"
1>&2 echo "files. It is meant to be used as a pre-flight check before"
1>&2 echo "deployment."
exit 1
fi

if [[ -d "$1" ]]; then
TERRAFORM_DIR="$1"
elif [[ -f "$1" ]]; then
TERRAFORM_PLAN_JSON="$1"
else
1>&2 echo "Error: $1 should be a file or directory"
exit 1

INPUT_PATH="$1"
if [[ "$INPUT_PATH" == "-" ]]; then
INPUT_PATH="$(mktemp -t input.XXXXXXX)"
cat - >"$INPUT_PATH"
fi
shift 1
REGO_PATHS=("$@")

# Prepend `-d` to every argument because `opa` expects to see many `-d`
# arguments.
D_REGO_PATHS=()
for p in "${REGO_PATHS[@]}"; do
D_REGO_PATHS+=('-d')
D_REGO_PATHS+=("$p")
done

function detect_input_type {
if [[ -d "$1" ]]; then
echo "terraform_dir"
return
fi

opa eval "${D_REGO_PATHS[@]}" -i "$1" --format pretty \
'data.fugue.input_type.input_type' | \
tr -d '"' # Unquote the string, not great but avoids jq dependency.
}

INPUT_TYPE="$(detect_input_type "$INPUT_PATH")"

# Setting this variable will cause terraform to print a little less information
# on what to do next.
export TF_IN_AUTOMATION=true
Expand All @@ -44,8 +66,10 @@ TERRAFORM="${TERRAFORM:-terraform}"

# Hide the output of a command only if it succeeds.
function silently {
local log="$(mktemp -t silently.XXXXXXX)"
local exit_code=""
local log
log="$(mktemp -t silently.XXXXXXX)"
local exit_code
exit_code=""
1>&2 echo "${1+$@}"
${1+"$@"} >"$log" 2>&1 || exit_code=$?
if [[ -n "$exit_code" ]]; then
Expand All @@ -56,34 +80,32 @@ function silently {
rm "$log"
}

if [[ -z "${TERRAFORM_PLAN_JSON:-}" ]]; then

# Preprocessing.
if [[ "$INPUT_TYPE" == "terraform_dir" ]]; then
# Temporary files.
TERRAFORM_PLAN="$(mktemp -t plan.XXXXXXX)"
TERRAFORM_PLAN_JSON="$(mktemp -t plan.json.XXXXXXX)"
TERRAFORM_PLAN_JSON="$TERRAFORM_PLAN.json"
function cleanup {
rm -f "$TERRAFORM_PLAN" "$TERRAFORM_PLAN_JSON"
rm -f "$TERRAFORM_PLAN" "$TERRAFORM_PLAN_JSON"
}
trap cleanup exit


# Run terraform to obtain the plan.
(cd "$TERRAFORM_DIR" &&
silently "$TERRAFORM" init &&
silently "$TERRAFORM" plan -refresh=false -out="$TERRAFORM_PLAN" &&
"$TERRAFORM" show -json "$TERRAFORM_PLAN" >"$TERRAFORM_PLAN_JSON")

(cd "$INPUT_PATH" &&
silently "$TERRAFORM" init &&
silently "$TERRAFORM" plan -refresh=false -out="$TERRAFORM_PLAN" &&
"$TERRAFORM" show -json "$TERRAFORM_PLAN" >"$TERRAFORM_PLAN_JSON")
INPUT_PATH="$TERRAFORM_PLAN_JSON"
elif [[ "$INPUT_TYPE" == "cloudformation" ]]; then
CFN_JSON="$(mktemp -t cfn.XXXXXXX)"
function cleanup {
rm -f "$CFN_JSON"
}
trap cleanup exit
cfn-flip -j "$INPUT_PATH" >"$CFN_JSON"
INPUT_PATH="$CFN_JSON"
fi

# Prepend `-d` to every argument because `opa` expects to see many `-d`
# arguments.
D_REGO_PATHS=()
for p in "${REGO_PATHS[@]}"; do
D_REGO_PATHS+=('-d')
D_REGO_PATHS+=("$p")
done

# Finally, run OPA on the result to get out our report.
opa eval -i "$TERRAFORM_PLAN_JSON" \
opa eval --input "$INPUT_PATH" \
"${D_REGO_PATHS[@]}" \
'data.fugue.regula.report'
13 changes: 13 additions & 0 deletions examples/aws/ec2_t2_only.rego
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Copyright 2020-2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package rules.ec2_t2_only

resource_type = "aws_instance"
Expand Down
13 changes: 13 additions & 0 deletions examples/aws/iam_password_length.rego
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Copyright 2020-2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package rules.iam_password_length

import data.fugue
Expand Down
14 changes: 14 additions & 0 deletions examples/aws/tag_all_resources.rego
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
# Copyright 2020-2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# The following rule checks that "taggable" resource types have tag
# values with at least 6 characters.
package rules.tag_all_resources
Expand Down
13 changes: 13 additions & 0 deletions examples/aws/useast1_only.rego
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
# Copyright 2020-2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package rules.useast1_only

import data.fugue
Expand Down
42 changes: 42 additions & 0 deletions lib/cfn/cloudtrail.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright 2020-2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package fugue.cfn.cloudtrail

import data.fugue.cfn.s3

data_resource_value_matches_bucket(value, bucket) {
s3.matches_bucket_name_or_id(bucket, value)
} {
is_string(value)
value == "arn:aws:s3:::"
} {
is_string(value)
# Workaround for fregot issue: we get a Subtype error from the concat() call if we
# just use bucket.BucketName. The type checking seems to work as expected if we
# pull bucket.BucketName out into its own variable.
name := bucket.BucketName
is_string(name)
pattern := concat("", ["arn:aws:s3:::", name, "(/.*)?$"])
regex.match(pattern, value)
} {
is_array(value)
s3.matches_bucket_name_or_id(bucket, value[_])
}

event_selector_applies_to_bucket(event_selector, bucket) {
data_resource := event_selector.DataResources[_]
data_resource.Type == "AWS::S3::Object"
value := data_resource.Values[_]
data_resource_value_matches_bucket(value, bucket)
}
31 changes: 31 additions & 0 deletions lib/cfn/lambda_library.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright 2020-2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package fugue.cfn.lambda_library

matches_function_name_or_id(function, value) {
value == function.id
} {
value == function.FunctionName
}

function_name_matches_function(function, function_name) {
matches_function_name_or_id(function, function_name)
} {
is_string(function_name)
parts := split(function_name, ":")
matches_function_name_or_id(function, parts[_])
} {
is_array(function_name)
matches_function_name_or_id(function, function_name[_])
}
66 changes: 66 additions & 0 deletions lib/cfn/nacl_library.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Copyright 2020-2021 Fugue, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
package cfn.nacl_library

import data.fugue

nacl_entries = fugue.resources("AWS::EC2::NetworkAclEntry")
nacl_ingress_by_id = {nacl_id: entries |
nacl_id = nacl_entries[_].NetworkAclId
entries = [entry |
entry = nacl_entries[_]
entry.NetworkAclId == nacl_id
object.get(entry, "Egress", false) == false
]
}

# Returns true if the NACL entry has an all-zero CIDR.
nacl_entry_zero_cidr(entry) {
entry.CidrBlock == "0.0.0.0/0"
} {
entry.Ipv6CidrBlock == "::/0"
}

# Returns true if the NACL entry includes the given port
nacl_entry_includes_port(entry, port) {
entry.Protocol == -1
} {
entry.PortRange.From == 0
entry.PortRange.To == 0
} {
entry.PortRange.From <= port
entry.PortRange.To >= port
}

# Check if there is a NACL entry that allows the given port from an all-zero
# CIDR.
nacl_ingress_zero_cidr_to_port(nacl_id, port) {
entries := [entry |
entry = nacl_ingress_by_id[nacl_id][_]
nacl_entry_zero_cidr(entry)
nacl_entry_includes_port(entry, port)
]

allows := [entry.RuleNumber | entry = entries[_]; entry.RuleAction == "allow"]
denies := [entry.RuleNumber | entry = entries[_]; entry.RuleAction == "deny"]

allow_precedes_denies(allows, denies)
}

allow_precedes_denies(allows, denies) {
_ = allows[_]
count(denies) == 0
} {
min(allows) < min(denies)
}
Loading

0 comments on commit bdf29b6

Please sign in to comment.