diff --git a/.github/workflows/golangci.yml b/.github/workflows/golangci.yml index d28964e..75c590f 100644 --- a/.github/workflows/golangci.yml +++ b/.github/workflows/golangci.yml @@ -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/* diff --git a/addlicense/main.go b/addlicense/main.go index c0e8963..0f67a53 100644 --- a/addlicense/main.go +++ b/addlicense/main.go @@ -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' { @@ -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, "", ";; ", "") @@ -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' { @@ -428,6 +464,7 @@ func hashBang(b []byte) []byte { return line } } + return nil } diff --git a/addlicense/testdata/expected/multiline-comment.sentinel b/addlicense/testdata/expected/multiline-comment.sentinel new file mode 100644 index 0000000..fe10952 --- /dev/null +++ b/addlicense/testdata/expected/multiline-comment.sentinel @@ -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 diff --git a/addlicense/testdata/expected/multiline-sharp.sentinel b/addlicense/testdata/expected/multiline-sharp.sentinel new file mode 100644 index 0000000..065c962 --- /dev/null +++ b/addlicense/testdata/expected/multiline-sharp.sentinel @@ -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 diff --git a/addlicense/testdata/expected/multiline-slash.sentinel b/addlicense/testdata/expected/multiline-slash.sentinel new file mode 100644 index 0000000..b224053 --- /dev/null +++ b/addlicense/testdata/expected/multiline-slash.sentinel @@ -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 diff --git a/addlicense/testdata/expected/no-policy.sentinel b/addlicense/testdata/expected/no-policy.sentinel new file mode 100644 index 0000000..762d7ff --- /dev/null +++ b/addlicense/testdata/expected/no-policy.sentinel @@ -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 +} diff --git a/addlicense/testdata/expected/singleline-slash.sentinel b/addlicense/testdata/expected/singleline-slash.sentinel new file mode 100644 index 0000000..f6a0789 --- /dev/null +++ b/addlicense/testdata/expected/singleline-slash.sentinel @@ -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 diff --git a/addlicense/testdata/initial/multiline-comment.sentinel b/addlicense/testdata/initial/multiline-comment.sentinel new file mode 100644 index 0000000..72e19b3 --- /dev/null +++ b/addlicense/testdata/initial/multiline-comment.sentinel @@ -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 diff --git a/addlicense/testdata/initial/multiline-sharp.sentinel b/addlicense/testdata/initial/multiline-sharp.sentinel new file mode 100644 index 0000000..d04b45f --- /dev/null +++ b/addlicense/testdata/initial/multiline-sharp.sentinel @@ -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 diff --git a/addlicense/testdata/initial/multiline-slash.sentinel b/addlicense/testdata/initial/multiline-slash.sentinel new file mode 100644 index 0000000..b738d9b --- /dev/null +++ b/addlicense/testdata/initial/multiline-slash.sentinel @@ -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 diff --git a/addlicense/testdata/initial/no-policy.sentinel b/addlicense/testdata/initial/no-policy.sentinel new file mode 100644 index 0000000..3ac85cb --- /dev/null +++ b/addlicense/testdata/initial/no-policy.sentinel @@ -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 +} diff --git a/addlicense/testdata/initial/singleline-slash.sentinel b/addlicense/testdata/initial/singleline-slash.sentinel new file mode 100644 index 0000000..505f9e9 --- /dev/null +++ b/addlicense/testdata/initial/singleline-slash.sentinel @@ -0,0 +1,10 @@ +// This policy requires that the `require_lowercase_characters` attribute of the `aws_iam_account_password_policy` + +# 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