Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ jobs:
version: latest
args: release --clean --skip=publish --snapshot

- uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
- uses: actions/upload-artifact@v4
with:
name: copywrite
path: dist/*
43 changes: 40 additions & 3 deletions addlicense/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ func addLicense(path string, fmode os.FileMode, tmpl *template.Template, data Li
return false, err
}

line := hashBang(b)
line := hashBang(b, path)
if len(line) > 0 {
b = b[len(line):]
if line[len(line)-1] != '\n' {
Expand Down Expand Up @@ -365,7 +365,7 @@ func licenseHeader(path string, tmpl *template.Template, data LicenseData) ([]by
lic, err = executeTemplate(tmpl, data, "/**", " * ", " */")
case ".cc", ".cpp", ".cs", ".go", ".hh", ".hpp", ".m", ".mm", ".proto", ".rs", ".swift", ".dart", ".groovy", ".v", ".sv", ".lr":
lic, err = executeTemplate(tmpl, data, "", "// ", "")
case ".py", ".sh", ".bash", ".zsh", ".yaml", ".yml", ".dockerfile", "dockerfile", ".rb", "gemfile", ".ru", ".tcl", ".hcl", ".tf", ".tfvars", ".nomad", ".bzl", ".pl", ".pp", ".ps1", ".psd1", ".psm1", ".txtar":
case ".py", ".sh", ".bash", ".zsh", ".yaml", ".yml", ".dockerfile", "dockerfile", ".rb", "gemfile", ".ru", ".tcl", ".hcl", ".tf", ".tfvars", ".nomad", ".bzl", ".pl", ".pp", ".ps1", ".psd1", ".psm1", ".txtar", ".sentinel":
lic, err = executeTemplate(tmpl, data, "", "# ", "")
case ".el", ".lisp":
lic, err = executeTemplate(tmpl, data, "", ";; ", "")
Expand Down Expand Up @@ -414,8 +414,44 @@ var head = []string{
"/** @jest-environment", // Jest Environment string https://jestjs.io/docs/configuration#testenvironment-string
}

func hashBang(b []byte) []byte {
// We need to skip the top file comments in sentinel files because they are are currently used to
// show policy text in UI in TFC. The patterns are created based on the comment format given
// in https://developer.hashicorp.com/sentinel/docs/language/spec#comments
var sentinelHeadPatterns = []string{
`^#.*\n?(#.*\n?)*\n`,
`^//.*\n?(//.*\n?)*\n`,
`^/\*.*\n?(.*\n?)*\*/\n\n`,
}

// matches regex patterns to extract headings to skip
func matchPattern(b []byte, path string) []byte {
base := strings.ToLower(filepath.Base(path))
var headPatterns []string
switch fileExtension(base) {
case ".sentinel":
headPatterns = sentinelHeadPatterns
default:
headPatterns = []string{}
}

for _, v := range headPatterns {
re := regexp.MustCompile(v)
match := re.Find(b)
if len(match) > 0 {
return match
}
}
return []byte{}
}

func hashBang(b []byte, path string) []byte {
var line []byte

line = matchPattern(b, path)
if len(line) > 0 {
return line
}

for _, c := range b {
line = append(line, c)
if c == '\n' {
Expand All @@ -428,6 +464,7 @@ func hashBang(b []byte) []byte {
return line
}
}

return nil
}

Expand Down
25 changes: 25 additions & 0 deletions addlicense/testdata/expected/multiline-comment.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy`
resource is according to CIS standards. */

# Copyright 2018 Google LLC
#
# 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.

# Imports

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps
27 changes: 27 additions & 0 deletions addlicense/testdata/expected/multiline-sharp.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy`
# resource is according to CIS standards.
#
# another text

# Copyright 2018 Google LLC
#
# 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.

# Imports

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps
25 changes: 25 additions & 0 deletions addlicense/testdata/expected/multiline-slash.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy`
// resource is according to CIS standards.

# Copyright 2018 Google LLC
#
# 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.

# Imports

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps
92 changes: 92 additions & 0 deletions addlicense/testdata/expected/no-policy.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Copyright 2018 Google LLC
#
# 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.

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps

# Constants

const = {
"resource_efs_file_system": "aws_efs_file_system",
"policy_name": "efs-encryption-at-rest-enabled",
"kms_key_id": "kms_key_id",
"constant_value": "constant_value",
"encrypted": "encrypted",
"encrypted_attr_violation_msg": "Attribute 'encrypted' should be true for 'aws_efs_file_system' resources. Refer to https://docs.aws.amazon.com/securityhub/latest/userguide/efs-controls.html#efs-1 for more details.",
"kms_key_id_attr_violation_msg": "Attribute 'kms_key_id' should be non empty for 'aws_efs_file_system' resources. Refer to https://docs.aws.amazon.com/securityhub/latest/userguide/efs-controls.html#efs-1 for more details.",
}

# Functions

build_violation_object = func(res, message) {
return {
"address": res.address,
"module_address": res.module_address,
"message": message,
}
}

# Variables

efs_file_systems_from_plan = tf.plan(tfplan.planned_values.resources).type(const.resource_efs_file_system).resources

# Filter out aws_efs_file_systems that have invalid 'encrypted' attribute
non_encrypted_file_systems = collection.reject(efs_file_systems_from_plan, func(res) {
encrypted_val = maps.get(res, "values.encrypted", false)
return encrypted_val is true
})

non_encrypted_file_systems_violations = map non_encrypted_file_systems as _, res {
build_violation_object(res, const.encrypted_attr_violation_msg)
}

efs_file_systems_from_configs = tf.config(tfconfig.resources).type(const.resource_efs_file_system).resources

# Filter out aws_efs_file_systems that have empty 'kms_key_id' attribute
efs_resources_with_empty_kms_key_ids = collection.reject(efs_file_systems_from_configs, func(res) {
key_path = "config.kms_key_id"
return maps.get(res, key_path, false) is not false and
maps.get(res, key_path + "." + const.constant_value, false) is not ""
})

efs_resources_with_empty_kms_key_ids_violations = map efs_resources_with_empty_kms_key_ids as _, res {
build_violation_object(res, const.kms_key_id_attr_violation_msg)
}

summary = {
"policy_name": const.policy_name,
"violations": non_encrypted_file_systems_violations + efs_resources_with_empty_kms_key_ids_violations,
}

# Outputs

print(report.generate_policy_report(summary))

# Rules

verify_non_encrypted_file_systems = rule {
non_encrypted_file_systems_violations is empty
}

verify_kms_key_referencing_file_systems = rule {
efs_resources_with_empty_kms_key_ids_violations is empty
}

main = rule {
verify_non_encrypted_file_systems and verify_kms_key_referencing_file_systems
}
24 changes: 24 additions & 0 deletions addlicense/testdata/expected/singleline-slash.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy`

# Copyright 2018 Google LLC
#
# 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.

# Imports

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps
11 changes: 11 additions & 0 deletions addlicense/testdata/initial/multiline-comment.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy`
resource is according to CIS standards. */

# Imports

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps
13 changes: 13 additions & 0 deletions addlicense/testdata/initial/multiline-sharp.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy`
# resource is according to CIS standards.
#
# another text

# Imports

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps
11 changes: 11 additions & 0 deletions addlicense/testdata/initial/multiline-slash.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy`
// resource is according to CIS standards.

# Imports

import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps
78 changes: 78 additions & 0 deletions addlicense/testdata/initial/no-policy.sentinel
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import "tfconfig/v2" as tfconfig
import "tfplan/v2" as tfplan
import "tfresources" as tf
import "report" as report
import "collection" as collection
import "collection/maps" as maps

# Constants

const = {
"resource_efs_file_system": "aws_efs_file_system",
"policy_name": "efs-encryption-at-rest-enabled",
"kms_key_id": "kms_key_id",
"constant_value": "constant_value",
"encrypted": "encrypted",
"encrypted_attr_violation_msg": "Attribute 'encrypted' should be true for 'aws_efs_file_system' resources. Refer to https://docs.aws.amazon.com/securityhub/latest/userguide/efs-controls.html#efs-1 for more details.",
"kms_key_id_attr_violation_msg": "Attribute 'kms_key_id' should be non empty for 'aws_efs_file_system' resources. Refer to https://docs.aws.amazon.com/securityhub/latest/userguide/efs-controls.html#efs-1 for more details.",
}

# Functions

build_violation_object = func(res, message) {
return {
"address": res.address,
"module_address": res.module_address,
"message": message,
}
}

# Variables

efs_file_systems_from_plan = tf.plan(tfplan.planned_values.resources).type(const.resource_efs_file_system).resources

# Filter out aws_efs_file_systems that have invalid 'encrypted' attribute
non_encrypted_file_systems = collection.reject(efs_file_systems_from_plan, func(res) {
encrypted_val = maps.get(res, "values.encrypted", false)
return encrypted_val is true
})

non_encrypted_file_systems_violations = map non_encrypted_file_systems as _, res {
build_violation_object(res, const.encrypted_attr_violation_msg)
}

efs_file_systems_from_configs = tf.config(tfconfig.resources).type(const.resource_efs_file_system).resources

# Filter out aws_efs_file_systems that have empty 'kms_key_id' attribute
efs_resources_with_empty_kms_key_ids = collection.reject(efs_file_systems_from_configs, func(res) {
key_path = "config.kms_key_id"
return maps.get(res, key_path, false) is not false and
maps.get(res, key_path + "." + const.constant_value, false) is not ""
})

efs_resources_with_empty_kms_key_ids_violations = map efs_resources_with_empty_kms_key_ids as _, res {
build_violation_object(res, const.kms_key_id_attr_violation_msg)
}

summary = {
"policy_name": const.policy_name,
"violations": non_encrypted_file_systems_violations + efs_resources_with_empty_kms_key_ids_violations,
}

# Outputs

print(report.generate_policy_report(summary))

# Rules

verify_non_encrypted_file_systems = rule {
non_encrypted_file_systems_violations is empty
}

verify_kms_key_referencing_file_systems = rule {
efs_resources_with_empty_kms_key_ids_violations is empty
}

main = rule {
verify_non_encrypted_file_systems and verify_kms_key_referencing_file_systems
}
Loading