Showing preview only (1,046K chars total). Download the full file or copy to clipboard to get everything.
Repository: stelligent/config-lint
Branch: master
Commit: 8e87d18df9df
Files: 666
Total size: 840.3 KB
Directory structure:
gitextract_py6281yd/
├── .devcontainer/
│ ├── Dockerfile
│ ├── build/
│ │ ├── Dockerfile
│ │ └── dockerhub.sh
│ └── devcontainer.json
├── .dockerhub/
│ └── Dockerfile
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── build_and_deploy.yml
│ ├── bump_version.yml
│ └── vscode_remote_development.yml
├── .gitignore
├── .goreleaser.yml
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── README.md
├── assertion/
│ ├── compare.go
│ ├── compare_test.go
│ ├── contains.go
│ ├── contains_test.go
│ ├── expression.go
│ ├── expression_test.go
│ ├── has_properties.go
│ ├── helper_test.go
│ ├── invoke.go
│ ├── invoke_test.go
│ ├── ip_operations.go
│ ├── ip_operations_test.go
│ ├── log.go
│ ├── match.go
│ ├── match_test.go
│ ├── rules.go
│ ├── rules_test.go
│ ├── search.go
│ ├── testdata/
│ │ ├── collection-assertions.yaml
│ │ ├── conditions.yaml
│ │ ├── default-severity.yaml
│ │ └── has-properties.yaml
│ ├── types.go
│ ├── util.go
│ ├── util_test.go
│ ├── value.go
│ └── value_test.go
├── cli/
│ ├── app.go
│ ├── app_test.go
│ ├── assets/
│ │ ├── lint-rules.yml
│ │ └── terraform/
│ │ └── aws/
│ │ ├── api_gateway/
│ │ │ └── api_gateway_domain_name/
│ │ │ └── security_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── security_policy.tf
│ │ │ └── test.yml
│ │ ├── batch/
│ │ │ └── batch_job_definition/
│ │ │ ├── aws_secrets/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── aws_secrets.tf
│ │ │ │ └── test.yml
│ │ │ └── container_properties_privileged/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── container_properties_privileged.tf
│ │ │ ├── terraform12/
│ │ │ │ └── container_properties_privileged.tf
│ │ │ └── test.yml
│ │ ├── cloudfront/
│ │ │ └── cloudfront_distribution/
│ │ │ ├── custom_origin_config/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── custom_origin_config.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── custom_origin_config.tf
│ │ │ │ └── test.yml
│ │ │ ├── logging_config/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── logging_config.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── logging_config.tf
│ │ │ │ └── test.yml
│ │ │ ├── minimum_ssl_protocol/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── minimum_ssl_protocol.tf
│ │ │ │ └── test.yml
│ │ │ └── viewer_protocol_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── viewer_protocol_policy.tf
│ │ │ ├── terraform12/
│ │ │ │ └── viewer_protocol_policy.tf
│ │ │ └── test.yml
│ │ ├── cloudtrail/
│ │ │ └── cloudtrail/
│ │ │ └── kms_key_id/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key_id.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key_id.tf
│ │ │ └── test.yml
│ │ ├── cloudwatch/
│ │ │ └── cloudwatch_log_destination_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── codebuild/
│ │ │ └── codebuild_project/
│ │ │ ├── artifact_encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── artifact_encryption.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── artifact_encryption.tf
│ │ │ │ └── test.yml
│ │ │ └── project_encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── project_encryption.tf
│ │ │ ├── terraform12/
│ │ │ │ └── project_encryption.tf
│ │ │ └── test.yml
│ │ ├── codepipeline/
│ │ │ └── codepipeline/
│ │ │ └── encryption_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encryption_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encryption_key.tf
│ │ │ └── test.yml
│ │ ├── dms/
│ │ │ └── dms_endpoint/
│ │ │ └── endpoint_kms_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key.tf
│ │ │ └── test.yml
│ │ ├── documentdb/
│ │ │ └── docdb_cluster/
│ │ │ ├── audit_logs/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── audit_logs.tf
│ │ │ │ └── test.yml
│ │ │ └── storage_encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── storage_encryption.tf
│ │ │ └── test.yml
│ │ ├── ec2/
│ │ │ ├── ami/
│ │ │ │ └── ebs_block_device_encrypted/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── ebs_block_device_encrypted.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── ebs_block_device_encrypted.tf
│ │ │ │ └── test.yml
│ │ │ ├── ami_copy/
│ │ │ │ └── encrypted/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ └── test.yml
│ │ │ ├── ebs_volume/
│ │ │ │ └── encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ └── test.yml
│ │ │ └── instance/
│ │ │ └── ebs_block_device_encrypted/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── ebs_block_device_encrypted.tf
│ │ │ └── test.yml
│ │ ├── ecr/
│ │ │ └── ecr_repository_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── ecs/
│ │ │ └── ecs_task_definition/
│ │ │ └── task_definition_secrets/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── secrets.tf
│ │ │ ├── terraform12/
│ │ │ │ └── secrets.tf
│ │ │ └── test.yml
│ │ ├── efs/
│ │ │ └── efs_file_system/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encrypted.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encrypted.tf
│ │ │ └── test.yml
│ │ ├── elastic_load_balancing/
│ │ │ ├── alb/
│ │ │ │ └── alb_access_logs_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ └── test.yml
│ │ │ ├── alb_listener/
│ │ │ │ └── alb_listener_https/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── https.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── https.tf
│ │ │ │ └── test.yml
│ │ │ ├── elb/
│ │ │ │ └── access_logs_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ └── test.yml
│ │ │ ├── lb/
│ │ │ │ └── access_logs_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ └── test.yml
│ │ │ └── lb_listener/
│ │ │ ├── listener_https/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── https.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── https.tf
│ │ │ │ └── test.yml
│ │ │ └── listener_ssl_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── ssl_policy.tf
│ │ │ ├── terraform12/
│ │ │ │ └── ssl_policy.tf
│ │ │ └── test.yml
│ │ ├── elasticache/
│ │ │ └── elasticache_replication_group/
│ │ │ ├── encryption_at_rest/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption_at_rest.tf
│ │ │ │ └── test.yml
│ │ │ └── encryption_in_transit/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encryption_in_transit.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encryption_in_transit.tf
│ │ │ └── test.yml
│ │ ├── elasticsearch/
│ │ │ ├── elasticsearch_domain/
│ │ │ │ ├── encryption_at_rest/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encryption_at_rest.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── encryption_node_to_node/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encryption_node_to_node.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── vpc_subnets/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── elasticsearch_vpc.tf
│ │ │ │ └── test.yml
│ │ │ └── shared/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ ├── elasticsearch_domain_policy_wildcard_principal.tf
│ │ │ │ └── elasticsearch_domain_wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── elastictranscoder/
│ │ │ └── elastictranscoder_pipeline/
│ │ │ └── require_encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── require_encryption.tf
│ │ │ └── test.yml
│ │ ├── emr/
│ │ │ └── emr_cluster/
│ │ │ └── logging/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── logging.tf
│ │ │ ├── terraform12/
│ │ │ │ └── logging.tf
│ │ │ └── test.yml
│ │ ├── glue/
│ │ │ └── glue_connection/
│ │ │ └── connection_properties/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── connection_properties.tf
│ │ │ └── test.yml
│ │ ├── iam/
│ │ │ ├── iam_group_membership/
│ │ │ │ └── group_and_users/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── group_and_users.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── group_and_users.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_policy/
│ │ │ │ ├── policy_action_wildcard/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── policy_notaction/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── policy_notresource/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── policy_resource_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_role/
│ │ │ │ ├── assume_role_policy_action_wildcard/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── assume_role_policy_action_wildcard.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── assume_role_policy_action_wildcard.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── assume_role_policy_notaction/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── assume_role_policy_notaction.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── assume_role_policy_notaction.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── assume_role_policy_notprincipal/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── assume_role_policy_notprincipal.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── assume_role_policy_notprincipal.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── assume_role_policy_version/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── assume_role_policy_version.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── assume_role_policy_version.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_role_policy/
│ │ │ │ ├── role_policy_action_wildcard/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── role_policy_notaction/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── role_policy_notresource/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── role_policy_resource_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_user_policy/
│ │ │ │ └── exists/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── resource_exists.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── resource_exists.tf
│ │ │ │ └── test.yml
│ │ │ └── iam_user_policy_attachment/
│ │ │ └── exists/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── resource_exists.tf
│ │ │ ├── terraform12/
│ │ │ │ └── resource_exists.tf
│ │ │ └── test.yml
│ │ ├── iot/
│ │ │ └── iot_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── kinesis/
│ │ │ └── kinesis_stream/
│ │ │ ├── kinesis_stream_encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encryption.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption.tf
│ │ │ │ └── test.yml
│ │ │ └── kinesis_stream_kms_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key.tf
│ │ │ └── test.yml
│ │ ├── kinesis_firehouse/
│ │ │ └── kinesis_firehose_delivery_stream/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encryption.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encryption.tf
│ │ │ └── test.yml
│ │ ├── kms/
│ │ │ └── kms_key/
│ │ │ ├── rotation/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── rotation.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── rotation.tf
│ │ │ │ └── test.yml
│ │ │ └── wildcard_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_policy.tf
│ │ │ └── test.yml
│ │ ├── lambda/
│ │ │ ├── lambda_function/
│ │ │ │ ├── encryption/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── encryption.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encryption.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── environment_variables_aws_secrets/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── environment_variables_aws_secrets.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── environment_variables_aws_secrets.tf
│ │ │ │ └── test.yml
│ │ │ └── lambda_permission/
│ │ │ ├── action/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── action.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── action.tf
│ │ │ │ └── test.yml
│ │ │ └── principal_wildcard/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── principal_wildcard.tf
│ │ │ ├── terraform12/
│ │ │ │ └── principal_wildcard.tf
│ │ │ └── test.yml
│ │ ├── mediastore/
│ │ │ └── media_store_container_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── neptune/
│ │ │ └── neptune_cluster/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── encryption.tf
│ │ │ └── test.yml
│ │ ├── opsworks/
│ │ │ └── opsworks_application/
│ │ │ └── require_ssl/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── require_ssl.tf
│ │ │ └── test.yml
│ │ ├── rds/
│ │ │ ├── db_instance/
│ │ │ │ ├── encryption/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── storage_encryption.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── storage_encryption.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── publicly_accessible/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ └── test.yml
│ │ │ └── rds_cluster/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── storage_encryption.tf
│ │ │ └── test.yml
│ │ ├── redshift/
│ │ │ ├── redshift_cluster/
│ │ │ │ ├── encrypted/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encrypted.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── enhanced_vpc_routing/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── enhanced_vpc_routing.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── enhanced_vpc_routing.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── kms_key_id/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── kms_key_id.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── kms_key_id.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── logging/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── logging.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── logging.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── publicly_accessible/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ └── test.yml
│ │ │ └── redshift_parameter_group/
│ │ │ ├── require_ssl/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── require_ssl.tf
│ │ │ │ └── test.yml
│ │ │ └── user_logging/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── user_logging.tf
│ │ │ └── test.yml
│ │ ├── s3/
│ │ │ ├── s3_bucket/
│ │ │ │ ├── acl_not_public/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── acl_not_public.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── acl_not_public.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── server_side_encryption_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── server_side_encryption_enabled.tf
│ │ │ │ └── test.yml
│ │ │ ├── s3_bucket_object/
│ │ │ │ └── encryption_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encryption_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption_enabled.tf
│ │ │ │ └── test.yml
│ │ │ └── s3_bucket_policy/
│ │ │ ├── policy_statement_action_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notaction/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notprincipal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_principal_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ └── policy_statement_secure_transport/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── policy_statement_secure_transport.tf
│ │ │ ├── terraform12/
│ │ │ │ └── policy_statement_secure_transport.tf
│ │ │ └── test.yml
│ │ ├── sagemaker/
│ │ │ ├── sagemaker_endpoint_configuration/
│ │ │ │ └── kms_key/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── kms_key.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── kms_key.tf
│ │ │ │ └── test.yml
│ │ │ └── sagemaker_notebook_instance/
│ │ │ └── kms_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key.tf
│ │ │ └── test.yml
│ │ ├── ses/
│ │ │ └── ses_identity_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── shared/
│ │ │ ├── exists.todo.txt
│ │ │ ├── https.todo.txt
│ │ │ ├── kms_key.todo.txt
│ │ │ ├── policy.todo.txt
│ │ │ ├── policy_version.todo.txt
│ │ │ └── require_ssl.todo.txt
│ │ ├── sns/
│ │ │ ├── shared/
│ │ │ │ └── wildcard_principal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ ├── sns_topic_policy_wildcard_principal.tf
│ │ │ │ │ └── sns_topic_wildcard_principal.tf
│ │ │ │ └── test.yml
│ │ │ └── sns_topic_policy/
│ │ │ ├── topic_policy_statement_notaction/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ └── test.yml
│ │ │ ├── topic_policy_statement_notprincipal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ └── test.yml
│ │ │ └── topic_policy_statement_principal_wildcard-copy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── policy_statement_principal_wildcard-copy.tf
│ │ │ ├── terraform12/
│ │ │ │ └── policy_statement_principal_wildcard-copy.tf
│ │ │ └── test.yml
│ │ ├── sqs/
│ │ │ ├── shared/
│ │ │ │ └── wildcard_principal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ ├── sqs_queue_policy_wildcard_principal.tf
│ │ │ │ │ └── sqs_queue_wildcard_principal.tf
│ │ │ │ └── test.yml
│ │ │ ├── sqs_queue/
│ │ │ │ └── encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encryption.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption.tf
│ │ │ │ └── test.yml
│ │ │ └── sqs_queue_policy/
│ │ │ ├── policy_statement_action_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notaction/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notprincipal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_principal_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ └── policy_version/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── policy_version.tf
│ │ │ ├── terraform12/
│ │ │ │ └── policy_version.tf
│ │ │ └── test.yml
│ │ ├── vpc/
│ │ │ ├── security_group/
│ │ │ │ ├── egress_all_protocols/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── egress_all_protocols.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── egress_all_protocols.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── egress_port_range/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── egress_port_range.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── egress_port_range.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── ingress_all_protocols/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── ingress_all_protocols.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── ingress_all_protocols.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── ingress_port_range/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── ingress_port_range.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── ingress_port_range.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── missing_egress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── missing_egress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── missing_egress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── non_32_ingress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── non_32_ingress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── non_32_ingress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── rdp_world_ingress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── rdp_world_ingress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── rdp_world_ingress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── ssh_world_ingress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── ssh_world_ingress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── ssh_world_ingress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── world_egress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── world_egress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── world_egress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── world_ingress/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── world_ingress.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── world_ingress.tf
│ │ │ │ └── test.yml
│ │ │ └── subnet/
│ │ │ └── map_public_ip_on_launch/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── map_public_ip_on_launch.tf
│ │ │ ├── terraform12/
│ │ │ │ └── map_public_ip_on_launch.tf
│ │ │ └── test.yml
│ │ └── waf/
│ │ └── waf_web_acl/
│ │ └── default_action_type/
│ │ ├── rule.yml
│ │ └── tests/
│ │ ├── terraform11/
│ │ │ └── default_action_type.tf
│ │ ├── terraform12/
│ │ │ └── default_action_type.tf
│ │ └── test.yml
│ ├── builtin_test.go
│ ├── options.go
│ ├── options_test.go
│ ├── report_writer.go
│ ├── report_writer_test.go
│ ├── terraform_test.go
│ └── testdata/
│ ├── builtin/
│ │ └── terraform12/
│ │ └── test.tf
│ ├── dirtest/
│ │ ├── a.yml
│ │ └── b.yml
│ ├── exclude-list
│ ├── profile-exceptions.yml
│ ├── profile.yml
│ ├── smoketest_exceptions.tf
│ ├── smoketest_tf11.tf
│ ├── smoketest_tf12.tf
│ ├── syntax-errors.yml
│ └── terraform.yml
├── docs/
│ ├── README.md
│ ├── conditions.md
│ ├── coverpage.md
│ ├── css/
│ │ └── style.css
│ ├── design.md
│ ├── development.md
│ ├── example-rules.md
│ ├── faq.md
│ ├── github_workflow.md
│ ├── index.html
│ ├── install.md
│ ├── operations.md
│ ├── output.md
│ ├── profiles.md
│ ├── rule_development.md
│ ├── rules.md
│ ├── running.md
│ ├── sidebar.md
│ ├── terraform.md
│ ├── tests.md
│ ├── value_from.md
│ └── yaml.md
├── example-files/
│ ├── config/
│ │ ├── cloudfront.tf
│ │ ├── elb.tf
│ │ ├── generic.config
│ │ ├── iam.tf
│ │ ├── my-pod.yml
│ │ ├── network-policy-1.yml
│ │ ├── network-policy-2.yml
│ │ ├── no-containers.yml
│ │ ├── not-a-pod.yml
│ │ ├── pod-nginx.yml
│ │ ├── pod-redis.yml
│ │ ├── policy.yml
│ │ ├── provider.tf
│ │ ├── s3-bucket-policy.tf
│ │ ├── s3-encryption.tf
│ │ ├── s3.tf
│ │ ├── search-debug.tf
│ │ ├── security_group.tf
│ │ ├── service-account.yml
│ │ ├── sns.tf
│ │ ├── sqs.tf
│ │ ├── terraform.tf
│ │ ├── variables.tf
│ │ ├── volumes.tf
│ │ └── web-and-helper.yml
│ ├── demo-resources/
│ │ └── s3-bucket.tf
│ └── rules/
│ ├── alias.yml
│ ├── generic-json.yml
│ ├── generic-yaml.yml
│ ├── iam-policies.yml
│ ├── iam-restricted.yml
│ ├── kubernetes.yml
│ ├── lint-rules-with-error.yml
│ ├── no-iam-actions.yml
│ ├── s3-encryption.yml
│ ├── terraform-more.yml
│ ├── terraform.yml
│ └── variables.tf
├── go.mod
├── go.sum
└── linter/
├── common.go
├── common_test.go
├── csv_resource_loader.go
├── csv_resource_loader_test.go
├── file_linter.go
├── file_linter_test.go
├── helpers_test.go
├── json_resource_loader.go
├── json_resource_loader_test.go
├── kubernetes.go
├── kubernetes_test.go
├── linter.go
├── linter_test.go
├── resource_linter.go
├── resource_linter_test.go
├── rules_resource_loader.go
├── rules_resource_loader_test.go
├── schema.go
├── terraform.go
├── terraform_interpolate.go
├── terraform_interpolate_test.go
├── terraform_test.go
├── terraform_v12.go
├── terraform_v12_test.go
├── testdata/
│ ├── data/
│ │ ├── bucket_name
│ │ ├── multi_line_content
│ │ ├── reference_relative.tf
│ │ ├── template_file_example_basic
│ │ ├── template_file_example_conditional
│ │ └── template_file_example_for_loop
│ ├── resources/
│ │ ├── batch_privileged.tf
│ │ ├── cloudfront_access_logs.tf
│ │ ├── defines_variables.tf
│ │ ├── dms_endpoint_encryption.tf
│ │ ├── dynamic_block.tf
│ │ ├── ec2_public.tf
│ │ ├── elasticache_encryption_rest.tf
│ │ ├── elasticache_encryption_transit.tf
│ │ ├── embedded_yaml.yml
│ │ ├── empty_document.yml
│ │ ├── emr_cluster_logs.tf
│ │ ├── explicit_chars.tf
│ │ ├── generic.config
│ │ ├── invalid.yml
│ │ ├── kinesis_kms_stream.tf
│ │ ├── kms_key_rotation.tf
│ │ ├── missing_kind.yml
│ │ ├── multi_level.tf
│ │ ├── multiple_blocks_same.tf
│ │ ├── multiple_pods.yml
│ │ ├── neptune_db_encryption.tf
│ │ ├── nullable_value.tf
│ │ ├── pod.yml
│ │ ├── policy_with_expression.tf
│ │ ├── policy_with_variables.tf
│ │ ├── rds_publicly_available.tf
│ │ ├── reference_file.tf
│ │ ├── reference_file_multi_line.tf
│ │ ├── reference_variables.tf
│ │ ├── sagemaker_endpoint_encryption.tf
│ │ ├── sagemaker_notebook_encryption.tf
│ │ ├── tagging.tf
│ │ ├── template_file_function_basic.tf
│ │ ├── template_file_function_conditional.tf
│ │ ├── template_file_function_for_loop.tf
│ │ ├── terraform_data.tf
│ │ ├── terraform_inner_objects.tf
│ │ ├── terraform_instance.tf
│ │ ├── terraform_module.tf
│ │ ├── terraform_policy.tf
│ │ ├── terraform_policy_empty.tf
│ │ ├── terraform_policy_invalid_json.tf
│ │ ├── terraform_provider.tf
│ │ ├── terraform_syntax_error.tf
│ │ ├── tf12_for_loop.tf
│ │ ├── tf12_resource_dependency.tf
│ │ ├── users.csv
│ │ ├── users.json
│ │ ├── uses_local_variables.tf
│ │ ├── uses_tf12_variables.tf
│ │ └── uses_variables.tf
│ └── rules/
│ ├── aggregate.yml
│ ├── bad-format.yml
│ ├── batch_definition.yml
│ ├── cloudfront_access_logs.yml
│ ├── dms_endpoint_encryption.yml
│ ├── dynamic_block.yml
│ ├── ec2_public.yml
│ ├── elasticache_encryption_rest.yml
│ ├── elasticache_encryption_transit.yml
│ ├── emr_cluster_logs.yml
│ ├── exclude_resource.yml
│ ├── explicit_chars.yml
│ ├── generic-csv.yml
│ ├── generic-json.yml
│ ├── generic-yaml.yml
│ ├── kinesis_kms_stream.yml
│ ├── kms_key_rotation.yml
│ ├── kubernetes.yml
│ ├── neptune_db_encryption.yml
│ ├── nullable_value.yml
│ ├── policy_variable.yml
│ ├── rds_publicly_available.yml
│ ├── rules.yml
│ ├── sagemaker_endpoint_encryption.yml
│ ├── sagemaker_notebook_encryption.yml
│ ├── tagging.yml
│ ├── terraform_bucket.yml
│ ├── terraform_data.yml
│ ├── terraform_instance.yml
│ ├── terraform_module.yml
│ ├── terraform_policy.yml
│ ├── terraform_provider.yml
│ ├── terraform_v12_variables.yml
│ ├── tf12_for_loop.yml
│ └── unknown.yml
├── tf12parser/
│ ├── README.md
│ ├── attribute.go
│ ├── block.go
│ ├── parser.go
│ ├── parser_test.go
│ ├── range.go
│ └── schema.go
├── yaml_resource_loader.go
└── yaml_resource_loader_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .devcontainer/Dockerfile
================================================
FROM stelligent/vscode-remote-config-lint:latest
================================================
FILE: .devcontainer/build/Dockerfile
================================================
FROM ubuntu:latest
# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive
# Install packages
RUN apt-get update \
# Install apt-utils and suppress package configuration warning
&& apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
# Install build tools
&& apt-get install -y \
bc \
unzip \
wget \
git \
g++ \
gcc \
libc6-dev \
make \
pkg-config \
ca-certificates \
gnupg-agent \
# Cleanup apt lists
&& rm -rf /var/lib/apt/lists/*
# Install Go
ENV GOLANG_VERSION 1.13.7
RUN set -eux; \
goRelArch='linux-amd64'; \
goRelSha256='b3dd4bd781a0271b33168e627f7f43886b4c5d1c794a4015abf34e99c6526ca3'; \
url="https://golang.org/dl/go${GOLANG_VERSION}.${goRelArch}.tar.gz"; \
wget -O go.tgz "$url"; \
echo "${goRelSha256} *go.tgz" | sha256sum -c -; \
tar -C /usr/local -xzf go.tgz; \
rm go.tgz
ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
RUN mkdir -p "$GOPATH/src" "$GOPATH/bin" && chmod -R 777 "$GOPATH"
ENV GO111MODULE=on
# Install VS Code Go Dependencies
RUN go get -x -d github.com/stamblerre/gocode \
&& go build -o gocode-gomod github.com/stamblerre/gocode \
&& mv gocode-gomod $GOPATH/bin/ \
&& go get -u -v \
golang.org/x/tools/gopls \
github.com/mdempsky/gocode \
# Workaround for https://github.com/uudashr/gopkgs/issues/25
github.com/uudashr/gopkgs/v2/cmd/gopkgs \
github.com/sqs/goreturns \
golang.org/x/lint/golint \
github.com/ramya-rao-a/go-outline \
&& go get github.com/go-delve/delve/cmd/dlv
# Create a non-root user
ARG USERNAME=config-lint-dev
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID --shell /bin/bash -m $USERNAME
# Prompt gpg window inside container for signing commits & setup folder permissions for non-root user
RUN echo 'export GPG_TTY="$(tty)"' >> /home/$USERNAME/.bashrc \
&& mkdir /home/$USERNAME/.gnupg \
&& chown -R $USERNAME:$USERNAME /home/$USERNAME/.gnupg
# Persist bash history between runs
RUN SNIPPET="export PROMPT_COMMAND='history -a' && export HISTFILE=/commandhistory/.bash_history" \
&& mkdir /commandhistory \
&& touch /commandhistory/.bash_history \
&& chown -R $USERNAME /commandhistory \
&& echo $SNIPPET >> "/home/$USERNAME/.bashrc"
# Add non-root user to $GOPATH
RUN chown -R $USERNAME $GOPATH \
# Add write permission for /go/pkg
&& chmod -R a+w /go/pkg
# Install Terraform
ARG TERRAFORM_VERSION=0.12.20
RUN cd /tmp \
&& wget https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \
&& unzip terraform_${TERRAFORM_VERSION}_linux_amd64.zip \
&& mv terraform /usr/local/bin/ \
&& rm terraform_${TERRAFORM_VERSION}_linux_amd64.zip
# Enter container as non-root user
USER $USERNAME
================================================
FILE: .devcontainer/build/dockerhub.sh
================================================
#!/bin/bash -ex
set +x
if [[ -z ${DOCKER_ORG} ]];
then
echo DOCKER_ORG must be set in the environment
exit 1
fi
if [[ -z ${GITHUB_SHA} ]];
then
echo GITHUB_SHA must be set in the environment
exit 1
fi
set -x
COMMIT_HASH=${GITHUB_SHA:0:8}
# publish vscode-remote docker image to DockerHub, https://hub.docker.com/r/stelligent/vscode-remote-config-lint
docker build -t $DOCKER_ORG/vscode-remote-config-lint:${COMMIT_HASH} --file .devcontainer/build/Dockerfile .
docker tag $DOCKER_ORG/vscode-remote-config-lint:${COMMIT_HASH} $DOCKER_ORG/vscode-remote-config-lint:latest
docker push $DOCKER_ORG/vscode-remote-config-lint:${COMMIT_HASH}
docker push $DOCKER_ORG/vscode-remote-config-lint:latest
================================================
FILE: .devcontainer/devcontainer.json
================================================
{
"name": "config-lint Development",
"dockerFile": "Dockerfile",
"appPort": 9000,
"remote.containers.workspaceMountConsistency": "consistent",
"mounts": [
// Bash History
"source=config-lint-bash_history,target=/commandhistory,type=volume"
],
"runArgs": [
// SSH
"-v", "${localEnv:HOME}/.ssh:/home/config-lint-dev/.ssh:ro",
// GPG
"-v", "${localEnv:HOME}/.gnupg/private-keys-v1.d:/home/config-lint-dev/.gnupg/private-keys-v1.d:ro",
"-v", "${localEnv:HOME}/.gnupg/pubring.kbx:/home/config-lint-dev/.gnupg/pubring.kbx:ro",
"-v", "${localEnv:HOME}/.gnupg/trustdb.gpg:/home/config-lint-dev/.gnupg/trustdb.gpg:ro"
],
"extensions": [
// General
"CoenraadS.bracket-pair-colorizer",
"fabiospampinato.vscode-diff",
"mrmlnc.vscode-duplicate",
"ms-azuretools.vscode-docker",
"wayou.vscode-todo-highlight",
// Go
"ms-vscode.go",
// Terraform
"mauve.terraform",
// JSON
"mohsen1.prettify-json",
// YAML
"redhat.vscode-yaml"
],
"settings": {
// Bracket Pair Colorizer
"bracketPairColorizer.forceUniqueOpeningColor": false,
"bracketPairColorizer.colorMode": "Consecutive",
"bracketPairColorizer.highlightActiveScope": true,
"bracketPairColorizer.activeScopeCSS": [
"borderStyle : solid",
"borderWidth : 1px",
"borderColor : {color}; opacity: 0.5",
"backgroundColor : {color}"
],
"editor.matchBrackets": "never",
"bracketPairColorizer.showBracketsInGutter": true,
// Go
"go.gopath": "/go",
"go.inferGopath": true,
"go.useLanguageServer": true,
"[go]": {
"editor.insertSpaces": true,
"editor.tabSize": 4,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
},
"gopls": {
"usePlaceholders": true
},
// Terraform
"[terraform]": {
"editor.formatOnSave": true
},
"terraform.languageServer": {
"enabled": false,
"args": []
},
"terraform.indexing": {
"enabled": false,
"liveIndexing": false
},
// YAML
"[yaml]": {
"editor.insertSpaces": true,
"editor.tabSize": 2
},
"yaml.format.enable": true,
"yaml.format.singleQuote": true,
"yaml.format.bracketSpacing": true,
"yaml.format.printWidth": 120,
"yaml.format.proseWrap": "always",
// TODO
"todohighlight.isEnable": true,
"todohighlight.isCaseSensitive": false
},
"postCreateCommand": "make deps"
}
================================================
FILE: .dockerhub/Dockerfile
================================================
FROM scratch
COPY config-lint /
ENTRYPOINT ["/config-lint"]
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
push:
branches-ignore:
- 'master'
tags-ignore:
- '**'
paths-ignore:
- 'docs/**'
- '**.md'
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: checkout
uses: actions/checkout@master
-
name: setup go
uses: actions/setup-go@v1
with:
go-version: '1.13'
-
name: dependencies
run: |
go mod download
-
name: build
run: |
export GOPATH=/home/runner/go
export PATH="$PATH:$GOPATH/bin"
make build
-
name: test
run: |
export GOPATH=/home/runner/go
export PATH="$PATH:$GOPATH/bin"
make test
make smoke-test
================================================
FILE: .github/workflows/build_and_deploy.yml
================================================
name: Build & Deploy
on:
push:
tags:
- 'v*.*.*'
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: checkout
uses: actions/checkout@master
-
name: setup go
uses: actions/setup-go@v1
with:
go-version: '1.13'
-
name: dependencies
run: |
go mod download
-
name: docker login
env:
DOCKER_USER: ${{ secrets.docker_user }}
DOCKER_PASSWORD: ${{ secrets.docker_password }}
run: |
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin
-
name: build
run: |
export GOPATH=/home/runner/go
export PATH="$PATH:$GOPATH/bin"
make build
-
name: test
run: |
export GOPATH=/home/runner/go
export PATH="$PATH:$GOPATH/bin"
make test
make smoke-test
-
name: release
uses: goreleaser/goreleaser-action@v1
with:
args: release --skip-validate
env:
GITHUB_TOKEN: ${{ secrets.gh_actions_token }}
================================================
FILE: .github/workflows/bump_version.yml
================================================
# Push with a commit message containing `#major` to bump major version
# and update the major version number here.
# MAJOR Version: 1.x
name: Bump Version
on:
push:
branches:
- master
tags-ignore:
- '**'
paths-ignore:
- 'docs/**'
- '**.md'
- '.devcontainer/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
fetch-depth: '0'
- name: Bump version and push tag
uses: anothrNick/github-tag-action@1.19.0
env:
GITHUB_TOKEN: ${{ secrets.gh_actions_token }}
WITH_V: true
DEFAULT_BUMP: minor
================================================
FILE: .github/workflows/vscode_remote_development.yml
================================================
name: VS Code DockerHub Build & Push
on:
push:
branches:
- 'master'
paths:
- '.devcontainer/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
-
name: checkout
uses: actions/checkout@master
-
name: docker login
env:
DOCKER_USER: ${{ secrets.docker_user }}
DOCKER_PASSWORD: ${{ secrets.docker_password }}
run: |
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin
-
name: Build & Push to DockerHub
env:
DOCKER_ORG: stelligent
run: bash ./.devcontainer/build/dockerhub.sh
================================================
FILE: .gitignore
================================================
# Local dev
dist/
.bundle
.DS_Store
.vscode/**/*
.release/
.idea/
.DS_Store
.test/
*/coverage.out
*/*packr.go
**/*.log
**/*.pem
**/*.retry
**/*.sw*
**/local.json
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Dependency directories (remove the comment below to include it)
vendor/
================================================
FILE: .goreleaser.yml
================================================
builds:
-
main: ./cli
env:
- CGO_ENABLED=0
goos:
- linux
- darwin
- windows
goarch:
- 386
- amd64
- arm
- arm64
archives:
- id: archive
name_template: '{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}'
replacements:
darwin: Darwin
linux: Linux
windows: Windows
386: i386
amd64: x86_64
format_overrides:
- goos: windows
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
brews:
-
github:
owner: stelligent
name: homebrew-tap
commit_author:
name: goreleaserbot
email: goreleaser@stelligent.com
folder: Formula
homepage: https://github.com/stelligent/config-lint
description: Validate configuration files using rules specified in YAML
test: |
system "#{bin}/config-lint -version"
dockers:
-
dockerfile: .dockerhub/Dockerfile
image_templates:
- "stelligent/config-lint:{{ .Tag }}"
- "stelligent/config-lint:latest"
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to config-lint
Help wanted! We'd love your contributions to config-lint. Please review the following guidelines before contributing. Also, feel free to propose changes to these guidelines by updating this file and submitting a pull request.
- [I have a question](#questions)
- [I found a bug](#bugs)
- [I have a feature request](#features)
- [I have a contribution to share](#process)
## <a name="questions"></a> Have a Question?
Please don't open a GitHub issue for questions about how to use config-lint, as the goal is to use issues for managing bugs and feature requests.
Until we have a better way to communicate with users, please use the [Stelligent contact page](https://stelligent.com/contact/) if you have any questions.
## <a name="bugs"></a> Found a Bug?
If you've identified a bug in config-lint, please [submit an issue](#issue) to our GitHub repo:
[stelligent/config-lint](https://github.com/stelligent/config-lint/issues/new). Please also feel free to submit a [Pull Request](#pr) with a fix for the bug!
## <a name="features"></a> Have a Feature Request?
All feature requests should start with [submitting an issue](#issue) documenting the user story and acceptance criteria. Again, feel free to submit a [Pull Request](#pr) with a proposed implementation of the feature.
## <a name="process"></a> Ready to Contribute!
### <a name="issue"></a> Create an issue
Before submitting a new issue, please search the issues to make sure there isn't a similar issue doesn't already exist.
Assuming no existing issues exist, please ensure you include the following bits of information when submitting the issue to ensure we can quickly reproduce your issue:
- The version of config-lint.
- The platform (Linux, OS X, Windows).
- The complete rule file(s) used if not using a built-in ruleset.
- The complete code the rule is running against.
- The complete command that was executed.
- Any output from the command.
- Details of the expected results and how they differed from the actual results.
We may have additional questions and will communicate through the GitHub issue, so please respond back to our questions to help reproduce and resolve the issue as quickly as possible.
New issues can be created with in our [GitHub
repo](https://github.com/stelligent/config-lint/issues/new).
### <a name="pr"></a>Pull Requests
Pull requests should target the `master` branch. Ensure you have a successful build for your branch. Please also reference the issue from the description of the pull request using [special keyword
syntax](https://help.github.com/articles/closing-issues-via-commit-messages/) to auto close the issue when the PR is merged. For example, include the phrase `fixes #14` in the PR description to have issue #14 auto close.
### <a name="style"></a> Styleguide
When submitting code, please make every effort to follow existing conventions and style in order to keep the code as readable as possible.
Here are a few points to keep in mind:
- Please run `make lint` before committing to ensure code aligns
with go standards.
- Please run `make cyclo` before committing to ensure cyclomatic complexity is lower than 15.
- Pleae ensure any new code is well tested and running `make test` is successful.
- Dependencies are managed with [go modules](https://blog.golang.org/using-go-modules) and dependency requirements are defined in [go.mod](go.mod)
### License
By contributing your code, you agree to license your contribution under the
terms of the [MIT License](LICENSE).
All files are released with the MIT license.
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2018-2020 Stelligent
Portions copyright 2019 Liam Galvin (https://github.com/liamg/tfsec)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
# Versioning based on latest git tag.
VERSION := $(shell git tag -l --sort=creatordate | grep "^v[0-9]*.[0-9]*.[0-9]*$$" | tail -1)
BUILD_DIR = .release
GOLDFLAGS = "-X main.version=$(VERSION)"
CLI_FILES = $(shell find cli linter assertion -name \*.go)
default: all
devdeps:
@echo "=== dev dependencies ==="
@go get "github.com/gobuffalo/packr/..."
@go get -u golang.org/x/lint/golint
@go get "github.com/fzipp/gocyclo"
deps:
@echo "=== dependencies ==="
go mod download
gen:
@echo "=== generating ==="
@go get "github.com/gobuffalo/packr/..."
@go generate ./...
lint: gen
@echo "=== linting ==="
@go vet ./...
@go get -u golang.org/x/lint/golint
@golint $(go list ./... | grep -v /vendor/)
cyclo:
@echo "=== cyclomatic complexity ==="
@go get "github.com/fzipp/gocyclo"
@gocyclo -over 15 assertion linter cli || echo "WARNING: cyclomatic complexity is high"
test: lint cyclo
@echo "=== testing ==="
@go test -v ./...
testtf: lint cyclo
@echo "=== testing Terraform Built In Rules ==="
@go test -v ./cli/... -run TestTerraformBuiltInRules
$(BUILD_DIR)/config-lint: $(CLI_FILES)
@echo "=== building config-lint - $@ ==="
mkdir -p $(BUILD_DIR)
GOOS=$(GOOS) GOARCH=$(GOARCH) go build -ldflags=$(GOLDFLAGS) -o $(BUILD_DIR)/config-lint cli/*.go
build: gen $(BUILD_DIR)/config-lint
all: clean deps test build smoke-test
dev: deps devdeps
clean:
@echo "=== cleaning ==="
rm -rf $(BUILD_DIR)
rm -rf vendor
rm -f cli/*-packr.go
cover-assertion:
@cd assertion && go test -coverprofile=coverage.out && go tool cover -html=coverage.out
cover-linter:
@cd linter && go test -coverprofile=coverage.out && go tool cover -html=coverage.out
cover-cli:
@cd cli && go test -coverprofile=coverage.out && go tool cover -html=coverage.out
smoke-test:
@$(BUILD_DIR)/config-lint -terraform cli/testdata/smoketest_tf12.tf
@$(BUILD_DIR)/config-lint -tfparser tf11 -terraform cli/testdata/smoketest_tf11.tf
@$(BUILD_DIR)/config-lint -tfparser tf11 -terraform -profile cli/testdata/profile-exceptions.yml cli/testdata/smoketest_exceptions.tf
================================================
FILE: README.md
================================================
[](https://img.shields.io/github/v/release/stelligent/config-lint?color=%233D9970)
[](https://github.com/stelligent/config-lint/workflows/Build%20%26%20Deploy/badge.svg)
[](https://goreportcard.com/report/github.com/stelligent/config-lint)
# 🔍 config-lint 🔎
A command line tool to validate configuration files using rules specified in YAML. The configuration files can be one of several formats: Terraform, JSON, YAML, with support for Kubernetes. There are built-in rules provided for Terraform, and custom files can be used for other formats.
📓 [Documentation](https://stelligent.github.io/config-lint)
👷 [Contributing](CONTRIBUTING.md)
🐛 [Issues & Bugs](https://github.com/stelligent/config-lint/issues)
## Blog Posts
✏️ [config-lint: Up and Running](https://stelligent.com/2020/04/17/config-lint-up-and-running/)
✏️ [Development Acceleration Through VS Code Remote Containers](https://stelligent.com/2020/04/10/development-acceleration-through-vs-code-remote-containers-setting-up-a-foundational-configuration/)
## Quick Start
Install the latest version of config-lint on macOS using [Homebrew](https://brew.sh/):
``` bash
brew tap stelligent/tap
brew install config-lint
```
Or manually on Linux:
``` bash
curl -L https://github.com/stelligent/config-lint/releases/latest/download/config-lint_Linux_x86_64.tar.gz | tar xz -C /usr/local/bin config-lint
chmod +rx /usr/local/bin/config-lint
```
Run the built-in ruleset against your Terraform files. For instance if you want to run config-lint against our [example files](example-files/):
``` bash
config-lint -terraform example-files/config
```
You will see failure and warning violations in the output like this:
``` bash
[
{
"AssertionMessage": "viewer_certificate[].cloudfront_default_certificate | [0] should be 'false', not ''",
"Category": "resource",
"CreatedAt": "2020-04-15T19:24:33Z",
"Filename": "example-files/config/cloudfront.tf",
"LineNumber": 10,
"ResourceID": "s3_distribution",
"ResourceType": "aws_cloudfront_distribution",
"RuleID": "CLOUDFRONT_MINIMUM_SSL",
"RuleMessage": "CloudFront Distribution must use TLS 1.2",
"Status": "FAILURE"
},
...
```
You can find more install options in our [installation guide](/docs/install.md).
================================================
FILE: assertion/compare.go
================================================
package assertion
import (
"strconv"
"time"
)
func intCompare(n1 int, n2 int) int {
if n1 < n2 {
return -1
}
if n1 > n2 {
return 1
}
return 0
}
func daysOld(data interface{}) int {
if stringValue, ok := data.(string); ok {
layout := "2006-01-02T15:04:05Z"
t, err := time.Parse(layout, stringValue)
if err != nil {
return 0
}
days := int(time.Since(t).Hours() / 24.0)
Debugf("Date: %v Days ago: %d\n", data, days)
return days
}
return 0
}
func compare(data interface{}, value string, valueType string) int {
switch valueType {
case "size":
n, _ := strconv.Atoi(value)
l := 0
switch v := data.(type) {
case []interface{}:
l = len(v)
case map[string]interface{}:
l = len(v)
}
return intCompare(l, n)
case "integer":
switch v := data.(type) {
case float64:
n1 := int(v)
n2, _ := strconv.Atoi(value)
return intCompare(n1, n2)
case int:
n2, _ := strconv.Atoi(value)
return intCompare(v, n2)
case string:
n1, _ := strconv.Atoi(v)
n2, _ := strconv.Atoi(value)
return intCompare(n1, n2)
}
return 0
case "age":
n, _ := strconv.Atoi(value)
return intCompare(daysOld(data), n)
default:
tmp, _ := JSONStringify(data)
s := unquoted(tmp)
if s > value {
return 1
}
if s < value {
return -1
}
return 0
}
}
================================================
FILE: assertion/compare_test.go
================================================
package assertion
import (
"github.com/stretchr/testify/assert"
"testing"
"time"
)
func TestDaysOldForToday(t *testing.T) {
now := time.Now().Format("2006-01-02T15:04:05Z")
assert.Equal(t, 0, daysOld(now), "Expecting daysOld to return 0")
}
func TestDaysOldFor90DaysAgo(t *testing.T) {
then := time.Now().Add(-time.Duration(90) * time.Hour * 24).Format("2006-01-02T15:04:05Z")
assert.Equal(t, 90, daysOld(then), "Expecting daysOld to return 90")
}
================================================
FILE: assertion/contains.go
================================================
package assertion
import (
"strings"
)
func interfaceListContains(v []interface{}, key, value string) (MatchResult, error) {
for _, element := range v {
if stringElement, isString := element.(string); isString {
if stringElement == value {
return matches()
}
if strings.Contains(stringElement, value) {
return matches()
}
}
}
return doesNotMatch("%v does not contain %v", key, value)
}
func stringListContains(v []string, key, value string) (MatchResult, error) {
for _, stringElement := range v {
if stringElement == value {
return matches()
}
if strings.Contains(stringElement, value) {
return matches()
}
}
return doesNotMatch("%v does not contain %v", key, value)
}
func stringContains(v string, key, value string) (MatchResult, error) {
if strings.Contains(v, value) {
return matches()
}
return doesNotMatch("%v does not contain %v", key, value)
}
func defaultContains(data interface{}, key, value string) (MatchResult, error) {
searchResult, err := JSONStringify(data)
if err != nil {
return matchError(err)
}
if strings.Contains(searchResult, value) {
return matches()
}
return doesNotMatch("%v does not contain %v", key, value)
}
func contains(data interface{}, key, value string) (MatchResult, error) {
switch v := data.(type) {
case []interface{}:
return interfaceListContains(v, key, value)
case []string:
return stringListContains(v, key, value)
case string:
return stringContains(v, key, value)
default:
return defaultContains(v, key, value)
}
}
func doesNotContain(data interface{}, key, value string) (MatchResult, error) {
m, err := contains(data, key, value)
if err != nil {
return matchError(err)
}
if m.Match {
return doesNotMatch("%v should not contain %v", key, value)
}
return matches()
}
func startsWith(data interface{}, key, prefix string) (MatchResult, error) {
switch v := data.(type) {
case string:
if strings.HasPrefix(v, prefix) {
return matches()
}
return doesNotMatch("%v does not start with %v", key, prefix)
default:
return doesNotMatch("%v is not a string %v", key, prefix)
}
}
func endsWith(data interface{}, key, suffix string) (MatchResult, error) {
switch v := data.(type) {
case string:
if strings.HasSuffix(v, suffix) {
return matches()
}
return doesNotMatch("%v does not end with %v", key, suffix)
default:
return doesNotMatch("%v is not a string %v", key, suffix)
}
}
================================================
FILE: assertion/contains_test.go
================================================
package assertion
import (
"github.com/stretchr/testify/assert"
"testing"
)
// The non error cases are covered in match_test
func TestContainsWithNonJSONType(t *testing.T) {
var complexNumber complex128
_, err := contains(complexNumber, "foo", "1")
if err == nil {
t.Errorf("Expecting contains to return an error for non JSON encodable data")
}
}
func TestDoesNotContainWithNonJSONType(t *testing.T) {
var complexNumber complex128
_, err := doesNotContain(complexNumber, "foo", "1")
if err == nil {
t.Errorf("Expecting doesNotContain to return an error for non JSON encodable data")
}
}
func TestContainsWithString(t *testing.T) {
s := "s3:Get*"
match, err := contains(s, "Action", "*")
assert.Nil(t, err, "Expecting no error from contains")
assert.True(t, match.Match, "Expecting match for string")
}
func TestContainsWithSliceOfStrings(t *testing.T) {
s := []string{"s3:Get*"}
match, err := contains(s, "Action", "*")
assert.Nil(t, err, "Expecting no error from contains")
assert.True(t, match.Match, "Expecting match for string")
}
================================================
FILE: assertion/expression.go
================================================
package assertion
func searchAndMatch(expression Expression, resource Resource) (MatchResult, error) {
v, err := SearchData(expression.Key, resource.Properties)
if err != nil {
return matchError(err)
}
match, err := isMatch(v, expression)
Debugf("ResourceID: %s Type: %s %v\n",
resource.ID,
resource.Type,
match)
return match, err
}
func orExpression(expressions []Expression, resource Resource) (MatchResult, error) {
for _, childExpression := range expressions {
match, err := booleanExpression(childExpression, resource)
if err != nil {
return matchError(err)
}
if match.Match {
return matches()
}
}
return doesNotMatch("Or expression fails") // TODO needs more information
}
func xorExpression(expressions []Expression, resource Resource) (MatchResult, error) {
matchCount := 0
for _, childExpression := range expressions {
match, err := booleanExpression(childExpression, resource)
if err != nil {
return matchError(err)
}
if match.Match {
matchCount++
}
}
if matchCount == 1 {
return matches()
}
return doesNotMatch("Xor expression fails") // TODO needs more information
}
func andExpression(expressions []Expression, resource Resource) (MatchResult, error) {
for _, childExpression := range expressions {
match, err := booleanExpression(childExpression, resource)
if err != nil {
return matchError(err)
}
if !match.Match {
return doesNotMatch("And expression fails: %s", match.Message)
}
}
return matches()
}
func notExpression(expressions []Expression, resource Resource) (MatchResult, error) {
// more than one child filter treated as not any
for _, childExpression := range expressions {
match, err := booleanExpression(childExpression, resource)
if err != nil {
return matchError(err)
}
if match.Match {
return doesNotMatch("Not expression fails") // TODO needs more information
}
}
return matches()
}
func collectResources(key string, resource Resource) ([]Resource, error) {
resources := make([]Resource, 0)
value, err := SearchData(key, resource.Properties)
if err != nil {
return resources, err
}
if collection, ok := value.([]interface{}); ok {
for _, properties := range collection {
collectionResource := Resource{
ID: resource.ID,
Type: resource.Type,
Properties: properties,
Filename: resource.Filename,
}
resources = append(resources, collectionResource)
}
}
return resources, nil
}
func everyExpression(collectionExpression CollectionExpression, resource Resource) (MatchResult, error) {
resources, err := collectResources(collectionExpression.Key, resource)
if err != nil {
return matchError(err)
}
for _, collectionResource := range resources {
match, err := andExpression(collectionExpression.Expressions, collectionResource)
if err != nil {
return matchError(err)
}
if !match.Match {
// at least one element is false, so entire expression is false
return doesNotMatch("Every expression fails: %s", match.Message)
}
}
// every element passes, so entire expression is true
return matches()
}
func someExpression(collectionExpression CollectionExpression, resource Resource) (MatchResult, error) {
resources, err := collectResources(collectionExpression.Key, resource)
if err != nil {
return matchError(err)
}
for _, collectionResource := range resources {
match, err := andExpression(collectionExpression.Expressions, collectionResource)
if err != nil {
return matchError(err)
}
// at least one element passes, so entire expression is true
if match.Match {
return matches()
}
}
// no element passes, so entire expression is false
return doesNotMatch("Some expression fails") // TODO needs more information
}
func noneExpression(collectionExpression CollectionExpression, resource Resource) (MatchResult, error) {
resources, err := collectResources(collectionExpression.Key, resource)
if err != nil {
return matchError(err)
}
for _, collectionResource := range resources {
match, err := andExpression(collectionExpression.Expressions, collectionResource)
if err != nil {
return matchError(err)
}
// at least one element passes, so entire expression is false
if match.Match {
return doesNotMatch("None expression fails: %s", match.Message)
}
}
// no element passes, so entire expression is true
return matches()
}
func exactlyOneExpression(collectionExpression CollectionExpression, resource Resource) (MatchResult, error) {
resources, err := collectResources(collectionExpression.Key, resource)
if err != nil {
return matchError(err)
}
matchCount := 0
for _, collectionResource := range resources {
match, err := andExpression(collectionExpression.Expressions, collectionResource)
if err != nil {
return matchError(err)
}
if match.Match {
matchCount++
}
}
if matchCount == 1 {
return matches()
}
return doesNotMatch("ExactlyOne expression fails")
}
func booleanExpression(expression Expression, resource Resource) (MatchResult, error) {
if expression.Or != nil && len(expression.Or) > 0 {
return orExpression(expression.Or, resource)
}
if expression.Xor != nil && len(expression.Xor) > 0 {
return xorExpression(expression.Xor, resource)
}
if expression.And != nil && len(expression.And) > 0 {
return andExpression(expression.And, resource)
}
if expression.Not != nil && len(expression.Not) > 0 {
return notExpression(expression.Not, resource)
}
if expression.Every.Key != "" {
return everyExpression(expression.Every, resource)
}
if expression.Some.Key != "" {
return someExpression(expression.Some, resource)
}
if expression.None.Key != "" {
return noneExpression(expression.None, resource)
}
if expression.ExactlyOne.Key != "" {
return exactlyOneExpression(expression.ExactlyOne, resource)
}
return searchAndMatch(expression, resource)
}
// ExcludeResource when resource.ID included in list of exceptions
func ExcludeResource(rule Rule, resource Resource) bool {
for _, id := range rule.Except {
if id == resource.ID {
return true
}
}
return false
}
// FilterResourceExceptions filters out resources that should not be validated
func FilterResourceExceptions(rule Rule, resources []Resource) []Resource {
if rule.Except == nil || len(rule.Except) == 0 {
return resources
}
filtered := make([]Resource, 0)
for _, resource := range resources {
if ExcludeResource(rule, resource) {
filtered = append(filtered, resource)
}
}
return filtered
}
// CheckExpression validates a single Resource using a single Expression
func CheckExpression(rule Rule, expression Expression, resource Resource) (Result, error) {
result := Result{
Status: "OK",
Message: "",
}
match, err := booleanExpression(expression, resource)
if err != nil {
DebugJSON("Error: ", err)
result.Status = "FAILURE"
result.Message = err.Error()
return result, err
}
if !match.Match {
if rule.Severity == "" {
result.Status = "FAILURE"
} else {
result.Status = rule.Severity
}
result.Message = match.Message
}
return result, nil
}
================================================
FILE: assertion/expression_test.go
================================================
package assertion
import (
"encoding/json"
"testing"
)
type ExpressionTestCase struct {
Rule Rule
Resource Resource
ExpectedStatus string
}
func TestCheckExpression(t *testing.T) {
simpleTestResource := Resource{
ID: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{
"instance_type": "t2.micro",
"ami": "ami-f2d3638a",
},
Filename: "test.tf",
}
resourceWithTags := Resource{
ID: "another_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{
"instance_type": "t2.micro",
"ami": "ami-f2d3638a",
"tags": map[string]interface{}{
"Environment": "Development",
"Project": "Web",
},
},
Filename: "test.tf",
}
resourceWithRootVolume := Resource{
ID: "another_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{
"instance_type": "t2.micro",
"ami": "ami-f2d3638a",
"root_block_device": map[string]interface{}{
"volume_size": "1000",
},
},
Filename: "test.tf",
}
testCases := map[string]ExpressionTestCase{
"testEq": {
Rule{
ID: "test1",
Message: "test rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
},
},
simpleTestResource,
"OK",
},
"testOr": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Or: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
Expression{
Key: "instance_type",
Op: "eq",
Value: "m4.large",
},
},
},
},
},
simpleTestResource,
"OK",
},
"testOrFails": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Or: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.nano",
},
Expression{
Key: "instance_type",
Op: "eq",
Value: "m4.large",
},
},
},
},
},
simpleTestResource,
"FAILURE",
},
"testXor": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Xor: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
Expression{
Key: "instance_type",
Op: "eq",
Value: "m4.large",
},
},
},
},
},
simpleTestResource,
"OK",
},
"testXorFails": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Xor: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
},
},
},
},
simpleTestResource,
"FAILURE",
},
"testXorFailsAgain": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Xor: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "m3.large",
},
Expression{
Key: "instance_type",
Op: "eq",
Value: "c4.large",
},
},
},
},
},
simpleTestResource,
"FAILURE",
},
"testAnd": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
And: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
Expression{
Key: "ami",
Op: "eq",
Value: "ami-f2d3638a",
},
},
},
},
},
simpleTestResource,
"OK",
},
"testAndFails": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
And: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "m3.medium",
},
Expression{
Key: "ami",
Op: "eq",
Value: "ami-f2d3638a",
},
},
},
},
},
simpleTestResource,
"FAILURE",
},
"testNot": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Not: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "c4.large",
},
},
},
},
},
simpleTestResource,
"OK",
},
"testNotFails": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Not: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
},
},
},
},
simpleTestResource,
"FAILURE",
},
"testNestedNot": {
Rule{
ID: "TEST1",
Message: "Test Rule",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Not: []Expression{
Expression{
Or: []Expression{
Expression{
Key: "instance_type",
Op: "eq",
Value: "t2.micro",
},
Expression{
Key: "instance_type",
Op: "eq",
Value: "m3.medium",
},
},
},
},
},
},
},
simpleTestResource,
"FAILURE",
},
"testSizeFails": {
Rule{
ID: "TESTCOUNT",
Message: "Test Resource Count Fails",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Key: "tags",
ValueType: "size",
Op: "eq",
Value: "3",
},
},
},
resourceWithTags,
"FAILURE",
},
"testSizeOK": {
Rule{
ID: "TESTCOUNT",
Message: "Test Resource Count OK",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Key: "tags",
ValueType: "size",
Op: "eq",
Value: "2",
},
},
},
resourceWithTags,
"OK",
},
"testIntegerFails": {
Rule{
ID: "TESTCOUNT",
Message: "Test integer Fails",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Key: "root_block_device.volume_size",
ValueType: "integer",
Op: "le",
Value: "500",
},
},
},
resourceWithRootVolume,
"FAILURE",
},
"testIntegerOK": {
Rule{
ID: "TESTCOUNT",
Message: "Test integer OK",
Severity: "FAILURE",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Key: "root_block_device.volume_size",
ValueType: "integer",
Op: "le",
Value: "2000",
},
},
},
resourceWithRootVolume,
"OK",
},
}
for k, tc := range testCases {
expressionResult, err := CheckExpression(tc.Rule, tc.Rule.Assertions[0], tc.Resource)
FailTestIfError(err, "TestSimple", t)
if expressionResult.Status != tc.ExpectedStatus {
t.Errorf("%s Failed Expected '%s' to be '%s'", k, expressionResult.Status, tc.ExpectedStatus)
}
}
}
func TestNestedBooleans(t *testing.T) {
rule := Rule{
ID: "TEST1",
Message: "Do not allow access to port 22 from 0.0.0.0/0",
Severity: "NOT_COMPLIANT",
Resource: "aws_instance",
Assertions: []Expression{
Expression{
Not: []Expression{
Expression{
And: []Expression{
Expression{
Key: "ipPermissions[].fromPort[]",
Op: "contains",
Value: "22",
},
Expression{
Key: "ipPermissions[].ipRanges[]",
Op: "contains",
Value: "0.0.0.0/0",
},
},
},
},
},
},
}
resource := Resource{
ID: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{},
Filename: "test.tf",
}
resourceJSON := `{
"description": "2017-12-03T03:14:29.856Z",
"groupName": "test-8246",
"ipPermissions": [
{
"fromPort": "22",
"ipProtocol": "tcp",
"toPort": "22",
"ipv4Ranges": [
{
"cidrIp": "0.0.0.0/0"
}
],
"ipRanges": [
"0.0.0.0/0"
]
}
]
}`
err := json.Unmarshal([]byte(resourceJSON), &resource.Properties)
if err != nil {
t.Error("Error parsing resource JSON")
}
expressionResult, err := CheckExpression(rule, rule.Assertions[0], resource)
FailTestIfError(err, "TestNestedBoolean", t)
if expressionResult.Status != "NOT_COMPLIANT" {
t.Error("Expecting nested boolean to return NOT_COMPLIANT")
}
}
func TestExceptions(t *testing.T) {
rule := Rule{
ID: "EXCEPT",
Except: []string{"200", "300"},
}
resources := []Resource{
Resource{ID: "100"},
Resource{ID: "200"},
Resource{ID: "300"},
Resource{ID: "400"},
}
filteredResources := FilterResourceExceptions(rule, resources)
if len(filteredResources) != 2 {
t.Error("Expecting exceptions to be removed from resource list")
}
}
func TestNoExceptions(t *testing.T) {
rule := Rule{
ID: "EXCEPT",
Except: []string{},
}
resources := []Resource{
Resource{ID: "100"},
Resource{ID: "200"},
Resource{ID: "300"},
Resource{ID: "400"},
}
filteredResources := FilterResourceExceptions(rule, resources)
if len(filteredResources) != 4 {
t.Error("Expecting no exceptions to return all resources")
}
}
func TestUsingFixtures(t *testing.T) {
fixtureFilenames := []string{
"./testdata/collection-assertions.yaml",
"./testdata/has-properties.yaml",
"./testdata/conditions.yaml",
"./testdata/default-severity.yaml",
}
for _, filename := range fixtureFilenames {
RunTestCasesFromFixture(filename, t)
}
}
================================================
FILE: assertion/has_properties.go
================================================
package assertion
import (
"strings"
)
func hasProperties(data interface{}, list string) (MatchResult, error) {
for _, key := range strings.Split(list, ",") {
if m, ok := data.(map[string]interface{}); ok {
if _, ok := m[key]; !ok {
return doesNotMatch("should have property %v", key)
}
}
}
return matches()
}
================================================
FILE: assertion/helper_test.go
================================================
package assertion
import (
"github.com/ghodss/yaml"
"io/ioutil"
"testing"
)
type (
// FixtureTestCases is used to read a set of test cases from a YAML file
FixtureTestCases struct {
Description string
TestCases []FixtureTestCase `json:"test_cases"`
}
// FixtureTestCase describes a single test case
FixtureTestCase struct {
Name string
Rule Rule
Resource Resource
Result string
}
)
// FailTestIfError is a helper to check err and call test Error if it is not nil
func FailTestIfError(err error, message string, t *testing.T) {
if err != nil {
t.Error(message + ":" + err.Error())
}
}
// LoadTestCasesFromFixture reads YAML data describing test cases
func LoadTestCasesFromFixture(filename string, t *testing.T) FixtureTestCases {
var testCases FixtureTestCases
content, err := ioutil.ReadFile(filename)
if err != nil {
t.Errorf("Unable to read fixture file: %s", filename)
return testCases
}
err = yaml.Unmarshal(content, &testCases)
if err != nil {
t.Errorf("Unable to parse fixture file: %s", filename)
return testCases
}
return testCases
}
// RunTestCasesFromFixture loads a YAML file describing test cases and runs them
func RunTestCasesFromFixture(filename string, t *testing.T) {
fixture := LoadTestCasesFromFixture(filename, t)
for _, testCase := range fixture.TestCases {
status, _, err := CheckRule(testCase.Rule, testCase.Resource, mockExternalRuleInvoker())
FailTestIfError(err, testCase.Name, t)
if status != testCase.Result {
t.Errorf("Test case %s returned %s expecting %s", testCase.Name, status, testCase.Result)
}
}
}
================================================
FILE: assertion/invoke.go
================================================
package assertion
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
// InvokeViolation has message describing a single validation error
type InvokeViolation struct {
Message string
}
// InvokeResponse contains a collection of validation errors
type InvokeResponse struct {
Violations []InvokeViolation
}
// StandardExternalRuleInvoker implements an external HTTP or HTTPS call
type StandardExternalRuleInvoker struct {
}
func makeViolation(rule Rule, resource Resource, message string) Violation {
return Violation{
RuleID: rule.ID,
Status: rule.Severity,
ResourceID: resource.ID,
ResourceType: resource.Type,
Category: resource.Category,
Filename: resource.Filename,
RuleMessage: rule.Message,
AssertionMessage: message,
CreatedAt: currentTime(),
}
}
func makeViolations(rule Rule, resource Resource, message string) []Violation {
v := makeViolation(rule, resource, message)
return []Violation{v}
}
// Invoke an external API to validate a Resource
func (e StandardExternalRuleInvoker) Invoke(rule Rule, resource Resource) (string, []Violation, error) {
status := "OK"
violations := make([]Violation, 0)
var payload interface{}
payload = resource
if rule.Invoke.Payload != "" {
p, err := SearchData(rule.Invoke.Payload, resource.Properties)
if err != nil {
return status, violations, err
}
payload = p
}
payloadJSON, err := JSONStringify(payload)
if err != nil {
violations := makeViolations(rule, resource, fmt.Sprintf("Unable to create JSON payload: %s", err.Error()))
return rule.Severity, violations, err
}
Debugf("Invoke %s on %s\n", rule.Invoke.URL, payloadJSON)
httpResponse, err := http.Post(rule.Invoke.URL, "application/json", bytes.NewBuffer([]byte(payloadJSON)))
if err != nil {
violations := makeViolations(rule, resource, fmt.Sprintf("Invoke failed: %s", err.Error()))
return rule.Severity, violations, err
}
if httpResponse.StatusCode != 200 {
violations := makeViolations(rule, resource, fmt.Sprintf("Invoke failed, StatusCode: %d", httpResponse.StatusCode))
return rule.Severity, violations, nil
}
defer httpResponse.Body.Close()
body, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
violations := makeViolations(rule, resource, "Invoke response cannot be read")
return rule.Severity, violations, nil
}
Debugf("Invoke body: %s\n", string(body))
var invokeResponse InvokeResponse
err = json.Unmarshal(body, &invokeResponse)
if err != nil {
violations := makeViolations(rule, resource, "Invoke response cannot be parsed")
return rule.Severity, violations, nil
}
for _, violation := range invokeResponse.Violations {
status = rule.Severity
v := makeViolation(rule, resource, violation.Message)
violations = append(violations, v)
}
return status, violations, nil
}
================================================
FILE: assertion/invoke_test.go
================================================
package assertion
import (
"encoding/json"
"fmt"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
)
func TestInvokeOK(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "{}")
}))
defer ts.Close()
i := StandardExternalRuleInvoker{}
rule := Rule{
Invoke: InvokeRuleAPI{
URL: ts.URL,
},
}
resource := Resource{}
status, violations, err := i.Invoke(rule, resource)
assert.Equal(t, "OK", status, "Expecting Invoke to return 'OK'")
assert.Equal(t, 0, len(violations), "Expecting Invoke to return no violations")
assert.Nil(t, err, "Expecting Invoke to not return an error")
}
func TestInvokeWithViolations(t *testing.T) {
response := InvokeResponse{
Violations: []InvokeViolation{
InvokeViolation{Message: "Something is not right"},
},
}
jsonData, err := json.Marshal(response)
assert.Nil(t, err, "Failed to marshal test response")
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, string(jsonData))
}))
defer ts.Close()
i := StandardExternalRuleInvoker{}
rule := Rule{
Severity: "FAILURE",
Invoke: InvokeRuleAPI{
URL: ts.URL,
},
}
resource := Resource{}
status, violations, err := i.Invoke(rule, resource)
assert.Equal(t, "FAILURE", status, "Expecting Invoke to return 'FAILURE'")
assert.Equal(t, 1, len(violations), "Expecting Invoke to return 1 violation")
assert.Nil(t, err, "Expecting Invoke to not return an error")
}
func TestInvokeSendsMetadata(t *testing.T) {
var invokedResource Resource
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body, _ := ioutil.ReadAll(r.Body)
_ = json.Unmarshal(body, &invokedResource)
fmt.Fprintln(w, "{}")
}))
defer ts.Close()
i := StandardExternalRuleInvoker{}
rule := Rule{
Invoke: InvokeRuleAPI{
URL: ts.URL,
},
}
resource := Resource{
Filename: "example.tf",
}
i.Invoke(rule, resource)
assert.Equal(t, resource.Filename, invokedResource.Filename, "Expecting filename metadata in request body")
}
================================================
FILE: assertion/ip_operations.go
================================================
package assertion
import (
"fmt"
"math"
"net"
"strconv"
"strings"
)
var rfc1918PrivateCIDRs = []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}
func getIPObject(addressString string) (net.IP, error) {
if !strings.Contains(addressString, "/") {
addressString = fmt.Sprintf("%s/32", addressString)
}
ipAddress, _, err := net.ParseCIDR(addressString)
if err != nil {
return nil, err
}
return ipAddress, nil
}
func isSubnet(ipAddressStr string, supernet string) bool {
ipAddress, parseError := getIPObject(ipAddressStr)
if parseError != nil {
Debugf("%v", parseError)
return false
}
_, superNetwork, err := net.ParseCIDR(supernet)
if err != nil {
Debugf("error parsing supernet: %v", err)
}
return superNetwork.Contains(ipAddress)
}
func isPrivateIP(ipAddressStr string) bool {
for _, cidr := range rfc1918PrivateCIDRs {
if isSubnet(ipAddressStr, cidr) {
return true
}
}
return false
}
func maxHostCount(ruleCidr string, hostLimitStr string) bool {
if !strings.Contains(ruleCidr, "/") {
ruleCidr = fmt.Sprintf("%s/32", ruleCidr)
}
hostLimit, convErr := strconv.Atoi(hostLimitStr)
if convErr != nil {
Debugf("error converting %v to int", hostLimitStr)
hostLimit = 0
}
_, network, err := net.ParseCIDR(ruleCidr)
if err != nil {
Debugf("error parsing ruleCidr: %v", ruleCidr)
return false
}
netmaskOnes, _ := network.Mask.Size()
return hostCountByNetmaskOnes(netmaskOnes) <= hostLimit
}
func hostCountByNetmaskOnes(netmaskOnes int) int {
return int(math.Pow(float64(2), float64(32-netmaskOnes)))
}
================================================
FILE: assertion/ip_operations_test.go
================================================
package assertion
import (
"testing"
)
var ipTests = []struct {
value string
supernet string
expectedResult bool
}{
{"1.1.1.1", "10.0.0.0/8", false},
{"1.1.1.1/32", "10.0.0.0/8", false},
{"10.1.0.0/16", "10.0.0.0/8", true},
{"10.1.1.1/32", "10.0.0.0/8", true},
{"10.1.1.1", "10.0.0.0/8", true},
}
func TestIsSubnet(t *testing.T) {
for _, input := range ipTests {
t.Run(input.value, func(t *testing.T) {
result := isSubnet(input.value, input.supernet)
if result != input.expectedResult {
t.Errorf("got %v, want %v", result, input.expectedResult)
}
})
}
}
var privateIPTests = []struct {
value string
expectedResult bool
}{
{"1.1.1.1", false},
{"1.1.1.1/32", false},
{"10.1.0.0/16", true},
{"10.1.1.1/32", true},
{"10.1.1.1", true},
{"172.16.0.0/12", true},
{"172.0.0.0/8", false},
{"172.16.1.1", true},
{"172.15.1.1", false},
{"192.168.1.1", true},
{"52.1.1.1", false},
{"sg-1234567", false},
}
func TestIsPrivateIp(t *testing.T) {
for _, input := range privateIPTests {
t.Run(input.value, func(t *testing.T) {
result := isPrivateIP(input.value)
if result != input.expectedResult {
t.Errorf("got %v, want %v", result, input.expectedResult)
}
})
}
}
var maxHostCountTests = []struct {
value string
max string
expectedResult bool
}{
{"10.0.0.0/8", "1000", false},
{"10.0.0.0/23", "500", false},
{"10.1.0.0/16", "65600", true},
{"10.1.1.1/32", "2", true},
{"10.1.1.1/32", "1", true},
{"10.1.1.1", "1", true},
{"sg-1234567", "0", false},
}
func TestMaxHostCount(t *testing.T) {
for _, input := range maxHostCountTests {
t.Run(input.value, func(t *testing.T) {
result := maxHostCount(input.value, input.max)
if result != input.expectedResult {
t.Errorf("got %v, want %v", result, input.expectedResult)
}
})
}
}
================================================
FILE: assertion/log.go
================================================
package assertion
import "fmt"
var (
isDebug = false
)
// SetDebug turns verbose logging on or off
func SetDebug(b bool) {
isDebug = b
}
// Debugf prints a formatted string when verbose logging is turned on
func Debugf(format string, args ...interface{}) {
if isDebug == false {
return
}
fmt.Printf(format, args...)
}
func DebugJSON(title string, object interface{}) {
if isDebug == false {
return
}
s, _ := JSONStringify(object)
fmt.Println(title)
fmt.Println(s)
}
================================================
FILE: assertion/match.go
================================================
package assertion
import (
"fmt"
"regexp"
"strings"
)
func matches() (MatchResult, error) {
return MatchResult{Match: true, Message: ""}, nil
}
func doesNotMatch(format string, args ...interface{}) (MatchResult, error) {
return MatchResult{
Match: false,
Message: fmt.Sprintf(format, args...),
}, nil
}
func matchError(err error) (MatchResult, error) {
return MatchResult{
Match: false,
Message: err.Error(),
}, err
}
func isMatch(data interface{}, expression Expression) (MatchResult, error) {
// FIXME eliminate searchResult this when all operations converted to use data
// individual ops can call JSONStringify as needed
searchResult, err := JSONStringify(data)
if err != nil {
return matchError(err)
}
searchResult = unquoted(searchResult)
key := expression.Key
op := expression.Op
value := expression.Value
valueType := expression.ValueType
switch op {
case "eq":
if compare(data, value, valueType) == 0 {
return matches()
}
return doesNotMatch("%v(%v) should be equal to %v", key, searchResult, value)
case "ne":
if compare(data, value, valueType) != 0 {
return matches()
}
return doesNotMatch("%v(%v) should not be equal to %v", key, searchResult, value)
case "lt":
if compare(data, value, valueType) < 0 {
return matches()
}
return doesNotMatch("%v(%v) should be less than %v", key, searchResult, value)
case "le":
if compare(data, value, valueType) <= 0 {
return matches()
}
return doesNotMatch("%v(%v) should be less than or equal to %v", key, searchResult, value)
case "gt":
if compare(data, value, valueType) > 0 {
return matches()
}
return doesNotMatch("%v(%v) should be greater than %v", key, searchResult, value)
case "ge":
if compare(data, value, valueType) >= 0 {
return matches()
}
return doesNotMatch("%v(%v) should be greater than or equal to %v", key, searchResult, value)
case "in":
for _, v := range strings.Split(value, ",") {
if v == searchResult {
return matches()
}
}
return doesNotMatch("%v(%v) should be in %v", key, searchResult, value)
case "not-in":
for _, v := range strings.Split(value, ",") {
if v == searchResult {
return doesNotMatch("%v(%v) should not be in %v", key, searchResult, value)
}
}
return matches()
case "absent":
if isAbsent(searchResult) {
return matches()
}
return doesNotMatch("%v should be absent", key)
case "present":
if isPresent(searchResult) {
return matches()
}
return doesNotMatch("%v should be present", key)
case "null":
if data == nil {
return matches()
}
return doesNotMatch("%v should be null", key)
case "not-null":
if data != nil {
return matches()
}
return doesNotMatch("%v should not be null", key)
case "empty":
if isEmpty(data) {
return matches()
}
return doesNotMatch("%v should be empty", key)
case "not-empty":
if !isEmpty(data) {
return matches()
}
return doesNotMatch("%v should not be empty", key)
case "is-array":
if isArray(data) {
return matches()
}
return doesNotMatch("%v should be an array", key)
case "is-not-array":
if !isArray(data) {
return matches()
}
return doesNotMatch("%v should not be an array", key)
case "intersect":
if jsonListsIntersect(searchResult, value) {
return matches()
}
return doesNotMatch("%v should intersect with %v", key, value)
case "contains":
return contains(data, key, value)
case "not-contains":
return doesNotContain(data, key, value)
case "does-not-contain":
return doesNotContain(data, key, value)
case "starts-with":
return startsWith(data, key, value)
case "ends-with":
return endsWith(data, key, value)
case "regex":
re, err := regexp.Compile(value)
if err != nil {
return matchError(err)
}
if re.MatchString(searchResult) {
return matches()
}
return doesNotMatch("%v(%v) should match %v", key, searchResult, value)
case "has-properties":
return hasProperties(data, value)
case "is-true":
if searchResult == "true" {
return matches()
}
return doesNotMatch("%v should be 'true', not '%v'", key, value)
case "is-false":
if searchResult == "false" {
return matches()
}
return doesNotMatch("%v should be 'false', not '%v'", key, value)
case "is-subnet":
isSubnet := isSubnet(searchResult, value)
if isSubnet {
return matches()
}
return doesNotMatch("%v should be a subnet of %v", searchResult, value)
case "is-private-ip":
isPrivate := isPrivateIP(searchResult)
if isPrivate {
return matches()
}
return doesNotMatch("%v should be a private ip", searchResult)
case "max-host-count":
hostCountWithinLimit := maxHostCount(searchResult, value)
if hostCountWithinLimit {
return matches()
}
return doesNotMatch("%v should be less than or equal to %v", searchResult, value)
}
return doesNotMatch("unknown op %v", op)
}
================================================
FILE: assertion/match_test.go
================================================
package assertion
import (
"encoding/json"
"fmt"
"testing"
)
type MatchTestCase struct {
SearchResult interface{}
Op string
Value string
ValueType string
ExpectedResult bool
}
func getQuotesRight(jsonString string) string {
if len(jsonString) == 0 {
return jsonString
}
if jsonString[0] != '[' {
jsonString = quoted(jsonString)
}
return jsonString
}
func unmarshal(s string) (interface{}, error) {
var searchResult interface{}
jsonString := getQuotesRight(s)
if len(jsonString) > 0 {
err := json.Unmarshal([]byte(jsonString), &searchResult)
if err != nil {
return "", err
}
}
return searchResult, nil
}
func TestIsMatch(t *testing.T) {
sliceOfTags := []interface{}{"Foo", "Bar"}
emptySlice := []interface{}{}
anotherSlice := []interface{}{"One", "Two"}
stringSlice := []string{"One", "Two"}
testCases := map[string]MatchTestCase{
"eqTrue": {"Foo", "eq", "Foo", "", true},
"eqFalse": {"Foo", "eq", "Bar", "", false},
"eqIntegerTrue": {22, "eq", "22", "integer", true},
"eqIntegerFalse": {80, "eq", "22", "integer", false},
"neFalse": {"Foo", "ne", "Foo", "", false},
"neTrue": {"Foo", "ne", "Bar", "", true},
"inTrue": {"Foo", "in", "Foo,Bar,Baz", "", true},
"inFalse": {"Foo", "in", "Bar,Baz", "", false},
"notInFalse": {"Foo", "not-in", "Foo,Bar,Baz", "", false},
"notInTrue": {"Foo", "not-in", "Bar,Baz", "", true},
"absentFalse": {"Foo", "absent", "", "", false},
"absentTrueForEmptyString": {"", "absent", "", "", true},
"absentTrueForNull": {"null", "absent", "", "", true},
"absentTrueForEmptyArray": {"[]", "absent", "", "", true},
"presentTrue": {sliceOfTags, "present", "", "", true},
"presentStringTrue": {"Foo", "present", "", "", true},
"presentFalseForNil": {nil, "present", "", "", false},
"presentFalseForEmptyString": {"", "present", "", "", false},
"presentFalseForNull": {"null", "present", "", "", false},
"presentFalseForEmptyArray": {"[]", "present", "", "", false},
"containsTrueForString": {"Foo", "contains", "oo", "", true},
"containsFalseForString": {"Foo", "contains", "aa", "", false},
"containsTrueForSlice": {sliceOfTags, "contains", "Bar", "", true},
"containsFalseForSubstring": {sliceOfTags, "contains", "abc", "", false},
"containsTrueForSliceOfStrings": {stringSlice, "contains", "One", "", true},
"containsFalseForSliceOfStrings": {stringSlice, "contains", "Three", "", false},
"containsTrueForInt": {1, "contains", "1", "", true},
"containsFalseForInt": {1, "contains", "One", "", false},
"notContainsFalseForString": {"Foo", "does-not-contain", "oo", "", false},
"notContainsTrueForString": {"Foo", "does-not-contain", "aa", "", true},
"notContainsFalseForSlice": {sliceOfTags, "does-not-contain", "Bar", "", false},
"notContainsTrueForSubstring": {sliceOfTags, "does-not-contain", "abc", "", true},
"regexTrueForEndOfString": {"Foo", "regex", "o$", "", true},
"regexFalseForEndOfString": {"Bar", "regex", "o$", "", false},
"regExTrueForBeginningOfString": {"Foo", "regex", "^F", "", true},
"regExFalseForBeginningOfString": {"Foo", "regex", "^B", "", false},
"reqExFalseForEntireString": {"Foo", "regex", "^Bar$", "", false},
"regExIgnoreCaseTrue": {"HTTPS", "regex", "(?i)https", "", true},
"regexIgnoreCaseFalse": {"HTTP", "regex", "(?i)https", "", false},
"ltTrue": {"a", "lt", "b", "", true},
"ltFalse": {"a", "lt", "a", "", false},
"leTrue": {"a", "le", "a", "", true},
"leFalse": {"b", "le", "a", "", false},
"gtTrue": {"b", "gt", "a", "", true},
"gtFalse": {"b", "gt", "b", "", false},
"geTrue": {"b", "ge", "b", "", true},
"geFalse": {"b", "ge", "c", "", false},
"nullTrue": {"", "null", "", "", true},
"nullFalse": {"1", "null", "", "", false},
"notNullFalse": {"", "not-null", "", "", false},
"notNullTrue": {"1", "not-null", "", "", true},
"emptyTrueForEmptyString": {"", "empty", "", "", true},
"emptyFalseForString": {"Foo", "empty", "", "", false},
"emptyTrueForEmptySlice": {emptySlice, "empty", "", "", true},
"emptyFalseForSlice": {sliceOfTags, "empty", "", "", false},
"notEmptyFalseForEmptyString": {"", "not-empty", "", "", false},
"notEmptyTrueForString": {"Foo", "not-empty", "", "", true},
"notEmptyFalseForEmptySlice": {emptySlice, "not-empty", "", "", false},
"notEmptyTrueForSlice": {sliceOfTags, "not-empty", "", "", true},
"intersectTrue": {"[\"one\",\"two\"]", "intersect", "[\"two\",\"three\"]", "", true},
"intersectFalse": {"[\"one\",\"two\"]", "intersect", "[\"three\",\"four\"]", "", false},
"eqSizeTrue": {anotherSlice, "eq", "2", "size", true},
"eqSizeFalse": {anotherSlice, "eq", "3", "size", false},
"isTrue": {"true", "is-true", "", "", true},
"isNotTrue": {"false", "is-true", "", "", false},
"isFalse": {"false", "is-false", "", "", true},
"isNotFalse": {"100", "is-false", "", "", false},
"startsWithTrue": {"FooBar", "starts-with", "Foo", "", true},
"startsWithFalse": {"FooBar", "starts-with", "Bar", "", false},
"startsWithNonString": {1, "starts-with", "Foo", "", false},
"endsWithTrue": {"FooBar", "ends-with", "Bar", "", true},
"endsWithFalse": {"FooBar", "ends-with", "Foo", "", false},
"endsartWithNonString": {1, "ends-with", "Foo", "", false},
"isArrayTrue": {sliceOfTags, "is-array", "", "", true},
"isArrayFalse": {"Foo", "is-array", "", "", false},
"isNotArrayTrue": {sliceOfTags, "is-not-array", "", "", false},
"isNotArrayFalse": {"Foo", "is-not-array", "", "", true},
}
for k, tc := range testCases {
var m MatchResult
var err error
expression := Expression{
Key: "key",
Op: tc.Op,
Value: tc.Value,
ValueType: tc.ValueType,
}
if s, isString := tc.SearchResult.(string); isString {
searchResult, err := unmarshal(s)
if err != nil {
fmt.Println(err)
t.Errorf("Unable to parse %s\n", tc.SearchResult)
}
m, err = isMatch(searchResult, expression)
} else {
m, err = isMatch(tc.SearchResult, expression)
}
if err != nil {
t.Errorf("%s Failed with error: %s", k, err.Error())
}
if m.Match != tc.ExpectedResult {
t.Errorf("%s Failed Expected '%s' %s '%s' to be %t", k, tc.SearchResult, tc.Op, tc.Value, tc.ExpectedResult)
}
}
}
================================================
FILE: assertion/rules.go
================================================
package assertion
import (
"errors"
"github.com/ghodss/yaml"
)
// ParseRules converts YAML string content to a Result
func ParseRules(rules string) (RuleSet, error) {
r := RuleSet{}
err := yaml.Unmarshal([]byte(rules), &r)
return r, err
}
// FilterRulesByTag selects a subset of rules based on a tag
func FilterRulesByTag(rules []Rule, tags []string) []Rule {
filteredRules := make([]Rule, 0)
for _, rule := range rules {
if tags == nil || listsIntersect(tags, rule.Tags) {
filteredRules = append(filteredRules, rule)
}
}
return filteredRules
}
// FilterRulesByID selectes a subset of rules based on ID
func FilterRulesByID(rules []Rule, ruleIDs []string, ignoreRuleIDs []string) []Rule {
if len(ruleIDs) == 0 && len(ignoreRuleIDs) == 0 {
return rules
}
filteredRules := make([]Rule, 0)
for _, rule := range rules {
include := false
for _, id := range ruleIDs {
if id == rule.ID {
include = true
}
}
if len(ignoreRuleIDs) > 0 {
include = true
for _, id := range ignoreRuleIDs {
if id == rule.ID {
include = false
}
}
}
if include {
filteredRules = append(filteredRules, rule)
}
}
return filteredRules
}
func uniqueRules(list []Rule) []Rule {
rules := make([]Rule, 0)
keys := make(map[string]bool, 0)
for _, rule := range list {
if _, ok := keys[rule.ID]; !ok {
keys[rule.ID] = true
rules = append(rules, rule)
}
}
return rules
}
// FilterRulesByTagAndID filters by both tag and id
func FilterRulesByTagAndID(rules []Rule, tags []string, ruleIds []string, ignoreRuleIds []string) []Rule {
if len(tags) == 0 && len(ruleIds) == 0 && len(ignoreRuleIds) == 0 {
return rules
}
if len(tags) == 0 {
return FilterRulesByID(rules, ruleIds, ignoreRuleIds)
}
if len(ruleIds) == 0 {
return FilterRulesByTag(rules, tags)
}
return uniqueRules(append(FilterRulesByID(rules, ruleIds, ignoreRuleIds), FilterRulesByTag(rules, tags)...))
}
// ResolveRules loads any dynamic values for a collection or rules
func ResolveRules(rules []Rule, valueSource ValueSource) ([]Rule, []Violation) {
resolvedRules := []Rule{}
violations := []Violation{}
for _, rule := range rules {
r, vs := ResolveRule(rule, valueSource)
resolvedRules = append(resolvedRules, r)
violations = append(violations, vs...)
}
return resolvedRules, violations
}
// ResolveRule loads any dynamic values for a single Rule
func ResolveRule(rule Rule, valueSource ValueSource) (Rule, []Violation) {
resolvedRule := rule
resolvedRule.Assertions = []Expression{}
violations := []Violation{}
for _, assertion := range rule.Assertions {
value, err := valueSource.GetValue(assertion)
if err != nil {
Debugf("ResolveRule error: %s\n", err.Error())
violations = append(violations, Violation{
Category: "load",
RuleID: "RULE_RESOLVE",
ResourceID: rule.ID,
ResourceType: "rule",
Status: "FAILURE",
RuleMessage: "Unable to resolve value in rule",
AssertionMessage: err.Error(),
CreatedAt: currentTime(),
})
}
resolvedAssertion := assertion
resolvedAssertion.Value = value
resolvedAssertion.ValueFrom = ValueFrom{}
resolvedRule.Assertions = append(resolvedRule.Assertions, resolvedAssertion)
}
return resolvedRule, violations
}
// CheckRule returns a list of violations for a single Rule applied to a single Resource
func CheckRule(rule Rule, resource Resource, e ExternalRuleInvoker) (string, []Violation, error) {
returnStatus := "OK"
violations := make([]Violation, 0)
if ExcludeResource(rule, resource) {
Debugf("Ignoring resource: %s", resource.ID)
return returnStatus, violations, nil
}
if rule.Invoke.URL != "" {
return e.Invoke(rule, resource)
}
match, err := andExpression(rule.Conditions, resource)
if err != nil {
return "FAILURE", violations, err
}
if !match.Match {
return returnStatus, violations, nil
}
for _, ruleAssertion := range rule.Assertions {
Debugf("Checking Category: %s, Type: %s, Id: %s\n", resource.Category, resource.Type, resource.ID)
expressionResult, err := CheckExpression(rule, ruleAssertion, resource)
if err != nil {
return "FAILURE", violations, err
}
if expressionResult.Status != "OK" {
// If the rule has category (e.g. Terraform rules), then return violations for that category only.
// If the rule has no category it will be applied to all resources as normal.
if rule.Category != "" && rule.Category != resource.Category {
break
}
returnStatus = expressionResult.Status
v := Violation{
RuleID: rule.ID,
ResourceID: resource.ID,
ResourceType: resource.Type,
Category: resource.Category,
Status: expressionResult.Status,
RuleMessage: rule.Message,
AssertionMessage: expressionResult.Message,
Filename: resource.Filename,
LineNumber: resource.LineNumber,
CreatedAt: currentTime(),
}
violations = append(violations, v)
}
}
return returnStatus, violations, nil
}
// Join two RuleSets together
func JoinRuleSets(firstSet RuleSet, secondSet RuleSet) (RuleSet, error) {
// if one of the sets is empty, return the other
// if both are empty, an empty set is returned
if len(firstSet.Rules) == 0 {
return secondSet, nil
} else if len(secondSet.Rules) == 0 {
return firstSet, nil
}
// RuleSets must match Type and Version
// Description will be taken from the first given rule set
if firstSet.Type != secondSet.Type || firstSet.Version != secondSet.Version {
return firstSet, errors.New("RuleSet Type and Version must match")
} else {
joinedSet := RuleSet{}
joinedSet.Type = firstSet.Type
joinedSet.Description = firstSet.Description
joinedSet.Files = append(firstSet.Files, secondSet.Files...)
joinedSet.Rules = append(firstSet.Rules, secondSet.Rules...)
joinedSet.Version = firstSet.Version
joinedSet.Resources = append(firstSet.Resources, secondSet.Resources...)
joinedSet.Columns = append(firstSet.Columns, secondSet.Columns...)
return joinedSet, nil
}
}
================================================
FILE: assertion/rules_test.go
================================================
package assertion
import (
"errors"
"testing"
)
// TestValueSource provides test values
type TestValueSource struct{}
func (t TestValueSource) GetValue(expression Expression) (string, error) {
if expression.Value != "" {
return expression.Value, nil
}
return "m3.medium", nil
}
func testValueSource() ValueSource {
return TestValueSource{}
}
// TestValueSourceWithError simulates errors for value provider
type TestValueSourceWithError struct{}
func (t TestValueSourceWithError) GetValue(expression Expression) (string, error) {
return "", errors.New("GET_VALUE_ERROR")
}
func testValueSourceWithError() ValueSource {
return TestValueSourceWithError{}
}
// MockExternalRuleInvoker simulates invocation of external endpoints to get values
type MockExternalRuleInvoker int
func mockExternalRuleInvoker() *MockExternalRuleInvoker {
var m MockExternalRuleInvoker
return &m
}
func (e *MockExternalRuleInvoker) Invoke(Rule, Resource) (string, []Violation, error) {
*e++
noViolations := make([]Violation, 0)
return "OK", noViolations, nil
}
var content = `Rules:
- id: TEST1
message: Test message
resource: aws_instance
severity: WARNING
assertions:
- key: instance_type
op: in
value: t2.micro
tags:
- ec2
- id: TEST2
message: Test message
resource: aws_s3_bucket
severity: WARNING
assertions:
- key: name
op: eq
value: bucket1
tags:
- s3
- id: TEST3
message: Test message
resource: aws_ebs_volume
severity: WARNING
assertions:
- key: size
op: le
value: 1000
value_type: integer
tags:
- ebs
`
func MustParseRules(content string, t *testing.T) RuleSet {
r, err := ParseRules(content)
if err != nil {
t.Error("Unable to parse:" + content)
}
return r
}
func TestParseRules(t *testing.T) {
r := MustParseRules(content, t)
if len(r.Rules) != 3 {
t.Error("Expected to parse 3 rules")
}
}
type FilterTestCase struct {
Tags []string
Ids []string
IgnoreIds []string
ExpectedRules []string
}
func TestFilterRules(t *testing.T) {
var emptyTags []string
var emptyIds []string
testCases := map[string]FilterTestCase{
"allRules": FilterTestCase{emptyTags, emptyIds, emptyIds, []string{"TEST1", "TEST2", "TEST3"}},
"tags": FilterTestCase{[]string{"s3"}, emptyIds, emptyIds, []string{"TEST2"}},
"rules": FilterTestCase{emptyTags, []string{"TEST1"}, emptyIds, []string{"TEST1"}},
"both": FilterTestCase{[]string{"s3"}, []string{"TEST1"}, emptyIds, []string{"TEST1", "TEST2"}},
"overlap": FilterTestCase{[]string{"s3"}, []string{"TEST2"}, emptyIds, []string{"TEST2"}},
"exclude": FilterTestCase{emptyTags, emptyIds, []string{"TEST1"}, []string{"TEST2", "TEST3"}},
}
for k, tc := range testCases {
r := FilterRulesByTagAndID(MustParseRules(content, t).Rules, tc.Tags, tc.Ids, tc.IgnoreIds)
if len(r) != len(tc.ExpectedRules) {
t.Errorf("Expected %s to include %d rules not %d\n", k, len(tc.ExpectedRules), len(r))
}
}
}
func TestFilterRulesByTagAndID(t *testing.T) {
tags := []string{"s3"}
ids := []string{"TEST3"}
r := FilterRulesByTagAndID(MustParseRules(content, t).Rules, tags, ids, []string{})
if len(r) != 2 {
t.Error("Expected filterRulesByTag to return 2 rules")
}
for _, rule := range r {
if rule.ID != "TEST2" && rule.ID != "TEST3" {
t.Error("Expected filterRulesByTagAndID to select correct rules")
}
}
}
var ruleWithMultipleFilters = `Rules:
- id: TEST1
message: Test message
resource: aws_instance
severity: FAILURE
assertions:
- key: instance_type
op: eq
value: t2.micro
- key: ami
op: eq
value: ami-000000
`
func TestRuleWithMultipleFilter(t *testing.T) {
rules := MustParseRules(ruleWithMultipleFilters, t)
resource := Resource{
ID: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{"instance_type": "t2.micro", "ami": "ami-000000"},
Filename: "test.tf",
}
status, violations, err := CheckRule(rules.Rules[0], resource, mockExternalRuleInvoker())
if err != nil {
t.Error("Error in CheckRule:" + err.Error())
}
if status != "OK" {
t.Error("Expecting multiple rule to match")
}
if len(violations) != 0 {
t.Error("Expecting multiple rule to have zero violations")
}
}
func TestMultipleFiltersWithSingleFailure(t *testing.T) {
rules := MustParseRules(ruleWithMultipleFilters, t)
resource := Resource{
ID: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{"instance_type": "t2.micro", "ami": "ami-111111"},
Filename: "test.tf",
}
status, violations, err := CheckRule(rules.Rules[0], resource, mockExternalRuleInvoker())
if err != nil {
t.Error("Error in CheckRule:" + err.Error())
}
if status != "FAILURE" {
t.Error("Expecting multiple rule to return FAILURE")
}
if len(violations) != 1 {
t.Error("Expecting multiple rule to have one violation")
}
}
func TestMultipleFiltersWithMultipleFailures(t *testing.T) {
rules := MustParseRules(ruleWithMultipleFilters, t)
resource := Resource{
ID: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{"instance_type": "c3.medium", "ami": "ami-111111"},
Filename: "test.tf",
}
status, violations, err := CheckRule(rules.Rules[0], resource, mockExternalRuleInvoker())
if err != nil {
t.Error("Error in CheckRule:" + err.Error())
}
if status != "FAILURE" {
t.Error("Expecting multiple rule to return FAILURE")
}
if len(violations) != 2 {
t.Error("Expecting multiple rule to have two violations")
}
}
var ruleWithValueFrom = `Rules:
- id: FROM1
message: Test value_from
severity: FAILURE
resource: aws_instance
assertions:
- key: instance_type
op: in
value_from:
bucket: config-rules-for-lambda
key: instance-types
`
func TestValueFrom(t *testing.T) {
rules := MustParseRules(ruleWithValueFrom, t)
resource := Resource{
ID: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{"instance_type": "m3.medium"},
Filename: "test.tf",
}
resolved, violations := ResolveRules(rules.Rules, testValueSource())
if len(violations) != 0 {
t.Errorf("Expecting ResolveRules to return 0 violations: %v", violations)
}
status, violations, err := CheckRule(resolved[0], resource, mockExternalRuleInvoker())
if err != nil {
t.Error("Error in CheckRule:" + err.Error())
}
if status != "OK" {
t.Error("Expecting value_from to match")
}
if len(violations) != 0 {
t.Error("Expecting value_from test to have 0 violations")
}
}
func TestResolveRuleError(t *testing.T) {
rules := MustParseRules(ruleWithValueFrom, t)
_, violations := ResolveRules(rules.Rules, testValueSourceWithError())
if len(violations) != 1 {
t.Errorf("Expecting ResolveRules to return 1 violations: %v", violations)
} else {
ruleID := violations[0].RuleID
if ruleID != "RULE_RESOLVE" {
t.Errorf("Expected RULE_RESOLVE violation, not %s", ruleID)
}
}
}
var ruleWithInvoke = `Rules:
- id: FROM1
message: Test value_from
severity: FAILURE
resource: aws_instance
invoke:
url: http://localhost
`
func TestInvokeRule(t *testing.T) {
rules := MustParseRules(ruleWithInvoke, t)
resource := Resource{
ID: "a_test_resource",
Type: "aws_instance",
Properties: map[string]interface{}{"instance_type": "m3.medium"},
Filename: "test.tf",
}
resolved, _ := ResolveRules(rules.Rules, testValueSource())
counter := mockExternalRuleInvoker()
CheckRule(resolved[0], resource, counter)
if *counter != 1 {
t.Error("Expecting external rule engine to be invoked")
}
}
================================================
FILE: assertion/search.go
================================================
package assertion
import (
"github.com/jmespath/go-jmespath"
)
// SearchData applies a JMESPath to a JSON object
func SearchData(expression string, data interface{}) (interface{}, error) {
if len(expression) == 0 {
return "null", nil
}
return jmespath.Search(expression, data)
}
================================================
FILE: assertion/testdata/collection-assertions.yaml
================================================
---
description: Test collection assertions
test_cases:
- name: every_OK
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- every:
key: "keys(@)"
expressions:
- key: "@"
op: in
value: Foo,Bar
resource:
id: collection_id
type: example
properties:
Foo:
- A
- B
- C
Bar:
- D
- E
result: OK
- name: every_FAILURE
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- every:
key: "keys(@)"
expressions:
- key: "@"
op: in
value: Foo,Bar
resource:
id: collection_id
type: example
properties:
Foo:
- A
- B
- C
Bar:
- D
- E
Baz:
- F
result: FAILURE
- name: every_multiple_assertions_FAILURE
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- every:
key: locations
expressions:
- key: city
op: present
- key: state
op: present
resource:
id: collection_id
type: example
properties:
locations:
- city: Seattle
state: WA
- city: San Francisco
result: FAILURE
- name: some_OK
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- some:
key: "keys(@)"
expressions:
- key: "@"
op: in
value: Foo,Bar
resource:
id: collection_id
type: example
properties:
Foo:
- A
- B
- C
Baz:
- D
- E
result: OK
- name: some_FAILURE
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- some:
key: "keys(@)"
expressions:
- key: "@"
op: in
value: Foo,Bar
resource:
id: collection_id
type: example
properties:
Baz:
- A
result: FAILURE
- name: none_OK
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- none:
key: "keys(@)"
expressions:
- key: "@"
op: in
value: Foo,Bar
resource:
id: collection_id
type: example
properties:
Baz:
- A
- B
result: OK
- name: none_with_multiple_assertions_OK
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- none:
key: "ipPermissions[]"
expressions:
- key: "fromPort"
op: eq
value: 22
value_type: integer
- key: "ipRanges[]"
op: contains
value: 0.0.0.0/0
resource:
id: collection_id
type: sample
properties:
ipPermissions:
- fromPort: 80
ipRanges:
- 0.0.0.0/0
result: OK
- name: none_FAILURE
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- none:
key: "keys(@)"
expressions:
- key: "@"
op: in
value: Foo,Bar
resource:
id: collection_id
type: example
properties:
Foo:
- A
Bar:
- B
result: FAILURE
- name: none_with_multiple_assertions_FAILURE
rule:
id: COLLECTION
message: Invalid key
severity: FAILURE
resource: sample
assertions:
- none:
key: "ipPermissions[]"
expressions:
- key: "fromPort"
op: eq
value: 22
value_type: integer
- key: "ipRanges[]"
op: contains
value: 0.0.0.0/0
resource:
id: collection_id
type: sample
properties:
ipPermissions:
- fromPort: 22
ipRanges:
- 0.0.0.0/0
result: FAILURE
- name: one_OK
rule:
id: COLLECTION
message: Duplicate names
severity: FAILURE
resource: example
assertions:
- exactly-one:
key: "tags[]"
expressions:
- key: name
op: eq
value: A
resource:
id: collection_id
type: example
properties:
tags:
- name: A
- name: B
result: OK
- name: one_FAILURE
rule:
id: COLLECTION
message: Duplicate names
severity: FAILURE
resource: example
assertions:
- exactly-one:
key: "tags[]"
expressions:
- key: name
op: eq
value: B
resource:
id: collection_id
type: example
properties:
tags:
- name: A
- name: B
- name: B
result: FAILURE
================================================
FILE: assertion/testdata/conditions.yaml
================================================
---
description: Test conditions
test_cases:
- name: conditions_false
rule:
id: CONDITIONS_1
message: Missing properties
severity: FAILURE
resource: sample
conditions:
- key: example.name
eq: first
assertions:
- key: example
op: has-properties
value: name,id
resource:
id: p1
type: sample
properties:
example:
name: first
id: 1
result: OK
- name: conditions_ignore
rule:
id: PROPERTIES_2
message: Ignore using condition
severity: FAILURE
resource: sample
conditions:
- key: example.name
op: eq
value: second
assertions:
- key: example
op: has-properties
value: name,id,description
resource:
id: p1
type: sample
properties:
example:
name: first
id: 1
result: OK
- name: conditions_FAILURE
rule:
id: PROPERTIES_2
message: Missing properties
severity: FAILURE
resource: sample
conditions:
- key: example.name
op: eq
value: first
assertions:
- key: example
op: has-properties
value: name,id,description
resource:
id: p1
type: sample
properties:
example:
name: first
id: 1
result: FAILURE
================================================
FILE: assertion/testdata/default-severity.yaml
================================================
---
description: Test uses default severity
test_cases:
- name: default-severity-FAILURE
rule:
id: PROPERTIES_1
message: Missing properties
resource: sample
assertions:
- key: example
op: has-properties
value: name,id
resource:
id: p1
type: sample
properties:
example:
name: first
result: FAILURE
================================================
FILE: assertion/testdata/has-properties.yaml
================================================
---
description: Test has-properties operator
test_cases:
- name: has-properties_OK
rule:
id: PROPERTIES_1
message: Missing properties
severity: FAILURE
resource: sample
assertions:
- key: example
op: has-properties
value: name,id
resource:
id: p1
type: sample
properties:
example:
name: first
id: 1
result: OK
- name: has-properties_FAILURE
rule:
id: PROPERTIES_2
message: Missing properties
severity: FAILURE
resource: sample
assertions:
- key: example
op: has-properties
value: name,id,description
resource:
id: p1
type: sample
properties:
example:
name: first
id: 1
result: FAILURE
================================================
FILE: assertion/types.go
================================================
package assertion
type (
// Resource describes a resource to be linted
Resource struct {
ID string `cty:"aws_instance"`
Type string
Category string // default is "resource", can be "data", "provider" for Terraform
Properties interface{}
Filename string
LineNumber int
}
// RuleSet describes a collection of rules for a Linter
RuleSet struct {
Type string
Description string
Files []string
Rules []Rule
Version string
Resources []ResourceConfig
Columns []ColumnConfig
Source string
}
// Rule is part of a RuleSet
Rule struct {
ID string
Message string
Severity string
Resource string
Resources []string
ExceptResources []string `json:"except_resources"`
Category string // default is "resource", can be "data", "provider", "module" for Terraform
Conditions []Expression
Assertions []Expression
Except []string
Tags []string
Invoke InvokeRuleAPI
}
// Expression expression for a Rule
Expression struct {
Key string
Op string
Value string
ValueType string `json:"value_type"`
ValueFrom ValueFrom `json:"value_from"`
Or []Expression
Xor []Expression
And []Expression
Not []Expression
Every CollectionExpression
Some CollectionExpression
None CollectionExpression
ExactlyOne CollectionExpression `json:"exactly-one"`
}
// CollectionExpression assertion for every element of a collection
CollectionExpression struct {
Key string
Expressions []Expression
}
// ValueFrom describes a external source for values
ValueFrom struct {
URL string
Variable string
}
// InvokeRuleAPI describes an external API for linting a resource
InvokeRuleAPI struct {
URL string
Payload string
}
// ResourceConfig describes how to discover resouces in a YAML file
ResourceConfig struct {
ID string
Type string
Key string
}
// ColumnConfig describes how to discover resources in a CSV file
ColumnConfig struct {
Name string
}
// ValidationReport summarizes validation for resources using rules
ValidationReport struct {
FilesScanned []string
Violations []Violation
ResourcesScanned []ScannedResource
}
// Violation has details for a failed assertion
Violation struct {
RuleID string
ResourceID string
ResourceType string
Category string
Status string
RuleMessage string
AssertionMessage string
Filename string
LineNumber int
CreatedAt string
}
// ScannedResource has details for each resource scanned
ScannedResource struct {
ResourceID string
ResourceType string
RuleID string
Status string
Filename string
LineNumber int
}
// ValueSource interface to fetch dynamic values
ValueSource interface {
GetValue(Expression) (string, error)
}
// ExternalRuleInvoker defines an interface for invoking an external API
ExternalRuleInvoker interface {
Invoke(Rule, Resource) (string, []Violation, error)
}
// MatchResult has a true/false result, but also includes a message for better reporting
MatchResult struct {
Match bool
Message string
}
// Result returns a status, along with a message
Result struct {
Status string
Message string
}
)
================================================
FILE: assertion/util.go
================================================
package assertion
import (
"encoding/json"
"fmt"
"path/filepath"
"time"
)
func unquoted(s string) string {
if s[0] == '"' {
return s[1 : len(s)-1]
}
return s
}
func quoted(s string) string {
return fmt.Sprintf("\"%s\"", s)
}
func isAbsent(s string) bool {
if s == "" || s == "null" || s == "[]" {
return true
}
return false
}
func isPresent(s string) bool {
return !isAbsent(s)
}
func isEmpty(data interface{}) bool {
switch v := data.(type) {
case nil:
return true
case string:
return len(v) == 0
case []interface{}:
return len(v) == 0
case []map[string]interface{}:
return len(v) == 0
default:
Debugf("isEmpty default: %v %T\n", data, data)
return false
}
}
func isArray(data interface{}) bool {
switch data.(type) {
case nil:
return false
case string:
return false
case []interface{}:
return true
case []map[string]interface{}:
return true
default:
return false
}
}
func listsIntersect(list1 []string, list2 []string) bool {
for _, a := range list1 {
for _, b := range list2 {
if a == b {
return true
}
}
}
return false
}
func jsonListsIntersect(s1 string, s2 string) bool {
var a1 []string
var a2 []string
err := json.Unmarshal([]byte(s1), &a1)
if err != nil {
return false
}
err = json.Unmarshal([]byte(s2), &a2)
if err != nil {
return false
}
return listsIntersect(a1, a2)
}
// ShouldIncludeFile return true if a filename matches one of a list of patterns
func ShouldIncludeFile(patterns []string, filename string) (bool, error) {
if filename == "-" { // always permit stdin
return true, nil
}
for _, pattern := range patterns {
_, file := filepath.Split(filename)
matched, err := filepath.Match(pattern, file)
if err != nil {
return false, err
}
if matched {
return true, nil
}
}
return false, nil
}
// FilterResourcesByType filters a list of resources that match a single resource type
func FilterResourcesByType(resources []Resource, resourceType string, resourceCategory string) []Resource {
if resourceType == "*" {
return resources
}
filtered := make([]Resource, 0)
for _, resource := range resources {
if resource.Type == resourceType && categoryMatches(resourceCategory, resource.Category) {
filtered = append(filtered, resource)
}
}
return filtered
}
// FilterResourcesByTypes filters a list of resources that match a slice of resource types
func FilterResourcesByTypes(resources []Resource, resourceTypes []string, resourceCategory string) []Resource {
filtered := make([]Resource, 0)
for _, resource := range resources {
if SliceContains(resourceTypes, resource.Type) && categoryMatches(resourceCategory, resource.Category) {
filtered = append(filtered, resource)
}
}
return filtered
}
func categoryMatches(c1, c2 string) bool {
if c1 == "" || c1 == "*" {
return true
}
return c1 == c2
}
// JSONStringify converts a JSON object into an indented string suitable for printing
func JSONStringify(data interface{}) (string, error) {
b, err := json.MarshalIndent(data, "", " ")
if err != nil {
return "", err
}
return string(b), nil
}
func currentTime() string {
return time.Now().UTC().Format(time.RFC3339)
}
func SliceContains(list []string, value string) bool {
for _, item := range list {
if item == value {
return true
}
}
return false
}
// Exclude resources
func ExcludeResourceTypes(resources []Resource, resourceTypes []string, resourceCategory string) []Resource {
filtered := make([]Resource, 0)
for _, resource := range resources {
if !SliceContains(resourceTypes, resource.Type) && categoryMatches(resourceCategory, resource.Category) {
filtered = append(filtered, resource)
}
}
return filtered
}
// FilterResourcesForRule returns resources applicable to the given rule
func FilterResourcesForRule(resources []Resource, rule Rule) []Resource {
if len(rule.Resources) > 0 {
Debugf("filtering rule resources on Resources slice")
return FilterResourcesByTypes(resources, rule.Resources, rule.Category)
}
if rule.Resource != "" && rule.Resource != "*" {
Debugf("filtering rule resources on Resource string")
return FilterResourcesByType(resources, rule.Resource, rule.Category)
}
if len(rule.ExceptResources) > 0 {
Debugf("filtering rule resources on ExceptResources slice")
return ExcludeResourceTypes(resources, rule.ExceptResources, rule.Category)
}
// default is to match all resources
return resources
}
================================================
FILE: assertion/util_test.go
================================================
package assertion
import (
"strings"
"testing"
)
func TestUnquotedWithoutQuotes(t *testing.T) {
if unquoted("Foo") != "Foo" {
t.Errorf("Unquoted for not quoted string fails")
}
}
func TestUnquotedWithQuotes(t *testing.T) {
if unquoted("\"Foo\"") != "Foo" {
t.Errorf("Unquoted for quoted string fails")
}
}
func TestIsAbsentEmptyString(t *testing.T) {
if isAbsent("") != true {
t.Errorf("isAbsent for empty string fails")
}
}
func TestIsAbsentEmptyArray(t *testing.T) {
if isAbsent("[]") != true {
t.Errorf("isAbsent for empty array fails")
}
}
func TestIsAbsentNull(t *testing.T) {
if isAbsent("null") != true {
t.Errorf("isAbsent for null fails")
}
}
func TestIsAbsentFalse(t *testing.T) {
if isAbsent("something") != false {
t.Errorf("isAbsent for value fails")
}
}
func TestIntersectTrue(t *testing.T) {
a := []string{"foo", "bar"}
b := []string{"bar", "baz"}
if listsIntersect(a, b) != true {
t.Errorf("listsIntersect should return true fails")
}
}
func TestIntersectFalse(t *testing.T) {
a := []string{"foo", "bar"}
b := []string{"baz"}
if listsIntersect(a, b) != false {
t.Errorf("listsIntersect should return false fails")
}
}
func TestJSONListsIntersectTrue(t *testing.T) {
s1 := "[ \"foo\", \"bar\" ]"
s2 := "[ \"baz\", \"bar\" ]"
if jsonListsIntersect(s1, s2) != true {
t.Errorf("JSONIntersect should return true")
}
}
func TestShouldIncludeFile(t *testing.T) {
patterns := []string{"*.tf", "*.yml"}
include, err := ShouldIncludeFile(patterns, "instance.tf")
if err != nil {
t.Errorf("ShouldIncludeFile generated an unexpected error: %v", err)
}
if !include {
t.Errorf("ShouldIncludeFile failed to include file with matching pattern")
}
}
func TestShouldNotIncludeFile(t *testing.T) {
patterns := []string{"*.tf", "*.yml"}
include, err := ShouldIncludeFile(patterns, "instance.config")
if err != nil {
t.Errorf("ShouldIncludeFile generated an unexpected error: %v", err)
}
if include {
t.Errorf("ShouldIncludeFile failed to exclude file with no matching pattern")
}
}
func TestFilterShouldIncludeResources(t *testing.T) {
resources := []Resource{
Resource{Type: "instance"},
Resource{Type: "volume"},
}
filtered := FilterResourcesByType(resources, "instance", "*")
if len(filtered) != 1 {
t.Errorf("FilterResourcesByType expected to match one resource")
}
}
func TestFilterShouldExcludeResources(t *testing.T) {
resources := []Resource{
Resource{Type: "instance"},
Resource{Type: "volume"},
}
filtered := FilterResourcesByType(resources, "database", "*")
if len(filtered) != 0 {
t.Errorf("FilterResourcesByType expected to match no resources")
}
}
func TestFilterShouldIncludeAllResources(t *testing.T) {
resources := []Resource{
Resource{Type: "instance"},
Resource{Type: "volume"},
}
filtered := FilterResourcesByType(resources, "*", "*")
if len(filtered) != len(resources) {
t.Errorf("FilterResourcesByType expected to include all resources")
}
}
func TestFilterShouldMatchCategoryForResources(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "template_file", Category: "data"},
}
filtered := FilterResourcesByType(resources, "template_file", "data")
if len(filtered) != 1 {
t.Errorf("FilterResourcesByType expected to match one resource")
}
}
func TestSliceContainsTrue(t *testing.T) {
test := []string{"x", "y", "z"}
isPresent := SliceContains(test, "x")
if isPresent != true {
t.Errorf("SliceContains expected to return true when a value is present")
}
}
func TestSliceContainsFalse(t *testing.T) {
test := []string{"x", "y", "z"}
isPresent := SliceContains(test, "a")
if isPresent != false {
t.Errorf("SliceContains expected to return false when a value is not present")
}
}
func TestFilterPluralShouldMatchMultipleResources(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "bucket", Category: "resource"},
}
filtered := FilterResourcesByTypes(resources, []string{"instance", "bucket"}, "resource")
if len(filtered) != 2 {
t.Errorf("FilterResourcesByTypes expected to match multiple types")
}
}
func TestFilterPluralShouldNotHaveUnlistedResources(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "bucket", Category: "resource"},
}
resourceTypes := []string{"instance"}
filtered := FilterResourcesByTypes(resources, resourceTypes, "resource")
if len(filtered) != 1 {
t.Errorf("FilterResourcesByTypes expected to match only %s", strings.Join(resourceTypes, ", "))
}
}
func TestFilterResourcesForRuleSlice(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "bucket", Category: "resource"},
}
rule := Rule{
Resources: []string{
"instance",
"bucket",
},
}
filtered := FilterResourcesForRule(resources, rule)
if len(filtered) != 2 {
t.Errorf("FilterResourcesForRule expected to return both resource types")
}
}
func TestFilterResourcesForRuleString(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "bucket", Category: "resource"},
}
rule := Rule{
Resource: "instance",
}
filtered := FilterResourcesForRule(resources, rule)
if len(filtered) != 1 {
t.Errorf("FilterResourcesForRule only expected to return one type")
}
}
func TestFilterResourcesForWildcard(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "bucket", Category: "resource"},
}
rule := Rule{
Resource: "*",
}
filtered := FilterResourcesForRule(resources, rule)
if len(filtered) != 2 {
t.Errorf("FilterResourcesForRule expected all resources to match")
}
}
func TestFilterResourcesForDefault(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "bucket", Category: "resource"},
}
rule := Rule{}
filtered := FilterResourcesForRule(resources, rule)
if len(filtered) != 2 {
t.Errorf("FilterResourcesForRule expected all resources to match")
}
}
func TestFilterExcludeResourcesForRuleString(t *testing.T) {
resources := []Resource{
Resource{Type: "instance", Category: "resource"},
Resource{Type: "bucket", Category: "resource"},
}
rule := Rule{
ExceptResources: []string{
"instance",
"security_group",
},
}
filtered := FilterResourcesForRule(resources, rule)
if len(filtered) != 1 {
t.Errorf("FilterResourcesForRule expected to return one type")
}
if len(filtered) > 0 && filtered[0].Type != "bucket" {
t.Errorf("FilterResourcesForRule expected to return bucket")
}
}
================================================
FILE: assertion/value.go
================================================
package assertion
import (
"bytes"
"errors"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// StandardValueSource can fetch values from external sources
type StandardValueSource struct {
Variables map[string]string
}
// GetValue looks up external values when an Expression includes a ValueFrom attribute
func (v StandardValueSource) GetValue(expression Expression) (string, error) {
if expression.ValueFrom.URL != "" {
Debugf("Getting value_from %s\n", expression.ValueFrom.URL)
parsedURL, err := url.Parse(expression.ValueFrom.URL)
if err != nil {
return "", err
}
switch strings.ToLower(parsedURL.Scheme) {
case "s3":
return v.GetValueFromS3(parsedURL.Host, parsedURL.Path)
case "http":
return v.GetValueFromHTTP(expression.ValueFrom.URL)
case "https":
return v.GetValueFromHTTP(expression.ValueFrom.URL)
default:
return "", fmt.Errorf("Unsupported protocol for value_from: %s", parsedURL.Scheme)
}
}
if expression.ValueFrom.Variable != "" {
if value, ok := v.Variables[expression.ValueFrom.Variable]; ok {
Debugf("Getting value_from variable %s: %s\n", expression.ValueFrom.Variable, value)
return value, nil
}
Debugf("Getting value_from variable %s not found\n", expression.ValueFrom.Variable)
return expression.ValueFrom.Variable, nil // or should this throw an error?
}
return expression.Value, nil
}
// GetValueFromS3 looks up external values for an Expression when the S3 protocol is specified
func (v StandardValueSource) GetValueFromS3(bucket string, key string) (string, error) {
region, err := getBucketRegion(bucket)
if err != nil {
message := fmt.Sprintf("Cannot get region for bucket %s: %s", bucket, err.Error())
return "", errors.New(message)
}
config := &aws.Config{Region: aws.String(region)}
awsSession := session.New()
s3Client := s3.New(awsSession, config)
response, err := s3Client.GetObject(&s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})
if err != nil {
message := fmt.Sprintf("Cannot read bucket %s key %s: %s", bucket, key, err.Error())
return "", errors.New(message)
}
buf := new(bytes.Buffer)
buf.ReadFrom(response.Body)
value := strings.TrimSpace(buf.String())
Debugf("Value from bucket %s key %s in region %s: %s\n", bucket, key, region, value)
return value, nil
}
func getBucketRegion(bucket string) (string, error) {
awsSession := session.New()
s3Client := s3.New(awsSession)
location, err := s3Client.GetBucketLocation(&s3.GetBucketLocationInput{
Bucket: aws.String(bucket),
})
if err != nil {
return "us-east-1", err
}
if location.LocationConstraint == nil {
// default region is us-east-1
return "us-east-1", nil
}
return *location.LocationConstraint, nil
}
// GetValueFromHTTP looks up external value for an Expression when the HTTP protocol is specified
func (v StandardValueSource) GetValueFromHTTP(url string) (string, error) {
httpResponse, err := http.Get(url)
if err != nil {
return "", err
}
if httpResponse.StatusCode != 200 {
return "", err
}
defer httpResponse.Body.Close()
body, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
return "", err
}
return strings.TrimSpace(string(body)), nil
}
================================================
FILE: assertion/value_test.go
================================================
package assertion
import (
"fmt"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
func TestCommandLineVariable(t *testing.T) {
s := StandardValueSource{
Variables: map[string]string{"foo": "bar"},
}
e := Expression{
ValueFrom: ValueFrom{Variable: "foo"},
}
v, err := s.GetValue(e)
if err != nil {
t.Errorf("Expected GetValue to return without error: %v\n", err.Error())
}
if v != "bar" {
t.Errorf("Expected GetValue to find variable 'foo' with value 'bar', not '%s'\n", v)
}
}
func TestValueFromHttp(t *testing.T) {
cidrBlock := "0.0.0.0/0"
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, cidrBlock)
}))
defer ts.Close()
s := StandardValueSource{}
e := Expression{
ValueFrom: ValueFrom{URL: ts.URL},
}
v, err := s.GetValue(e)
assert.Nil(t, err, "Expecting GetValue to not return an error")
assert.Equal(t, cidrBlock, v, "Expecting CIDR value to be returned")
}
================================================
FILE: cli/app.go
================================================
package main
//go:generate packr -v
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/ghodss/yaml"
"github.com/gobuffalo/packr"
"github.com/stelligent/config-lint/assertion"
"github.com/stelligent/config-lint/linter"
)
var version string
type (
// LinterOptions for applying rules
LinterOptions struct {
Tags []string
RuleIDs []string
IgnoreRuleIDs []string
QueryExpression string
SearchExpression string
ExcludePatterns []string
Variables map[string]string
TerraformParser string
}
// ProfileOptions for default options from a project file
ProfileOptions struct {
Rules []string
IDs []string
IgnoreIDs []string `json:"ignore_ids"`
Tags []string
Query string
Files []string
Terraform bool
Exceptions []RuleException
Variables map[string]string
ExcludePatterns []string `json:"exclude"`
ExcludeFromFilenames []string `json:"exclude_from"`
}
// RuleException optional list allowing a project to ignore specific rules for specific resources
RuleException struct {
RuleID string
ResourceCategory string
ResourceType string
ResourceID string
Comments string
}
// CommandLineOptions for collecting options from the command line
CommandLineOptions struct {
RulesFilenames arrayFlags
ExcludePatterns arrayFlags
ExcludeFromFilenames arrayFlags
Variables arrayFlags
TerraformParser *string
ProfileFilename *string
TerraformBuiltInRules *bool
Tags *string
Ids *string
IgnoreIds *string
QueryExpression *string
VerboseReport *bool
SearchExpression *string
Validate *bool
Version *bool
Debug *bool
Args []string
}
// ReportWriter formats and displays a ValidationReport
ReportWriter interface {
WriteReport(assertion.ValidationReport, LinterOptions)
}
// DefaultReportWriter writes the report to Stdout
DefaultReportWriter struct {
Writer io.Writer
}
)
func main() {
commandLineOptions := getCommandLineOptions()
if *commandLineOptions.Version == true {
fmt.Println(version)
return
}
if *commandLineOptions.Debug == true {
assertion.SetDebug(true)
}
if *commandLineOptions.Validate {
exitCode, err := validateRules(commandLineOptions.Args, DefaultReportWriter{Writer: os.Stdout})
if err != nil {
fmt.Println(err.Error())
}
os.Exit(exitCode)
}
profileOptions, err := loadProfile(*commandLineOptions.ProfileFilename)
if err != nil {
fmt.Printf("Error loading profile: %v\n", err)
os.Exit(-1)
}
rulesFilenames := loadFilenames(commandLineOptions.RulesFilenames, profileOptions.Rules)
configFilenames := defaultToCurrentDirectory(loadFilenames(commandLineOptions.Args, profileOptions.Files))
useTerraformBuiltInRules := *commandLineOptions.TerraformBuiltInRules || profileOptions.Terraform
if err != nil {
fmt.Printf("Unable to load exclude patterns: %s\n", err)
os.Exit(-1)
}
linterOptions, err := getLinterOptions(commandLineOptions, profileOptions)
if err != nil {
fmt.Printf("Failed to parse options: %v\n", err)
os.Exit(-1)
}
ruleSets, err := loadRuleSets(rulesFilenames)
if err != nil {
fmt.Printf("Failed to load rules: %v\n", err)
os.Exit(-1)
}
// Same rule set applies to both TerraformBuiltInRules and Terraform11BuiltInRules
// loadBuiltInRuleSet can be called recursively against a directory, as done here,
// or can be called against a single file, as done with lint-rule.yml
if useTerraformBuiltInRules {
builtInRuleSet, err := loadBuiltInRuleSet("terraform/")
if err != nil {
fmt.Printf("Failed to load built-in rules for Terraform: %v\n", err)
os.Exit(-1)
}
ruleSets = append(ruleSets, builtInRuleSet)
}
if len(ruleSets) == 0 {
fmt.Println("No rules")
os.Exit(-1)
}
ruleSets = addExceptions(ruleSets, profileOptions.Exceptions)
os.Exit(applyRules(ruleSets, configFilenames, linterOptions, DefaultReportWriter{Writer: os.Stdout}))
}
func addExceptions(ruleSets []assertion.RuleSet, exceptions []RuleException) []assertion.RuleSet {
sets := []assertion.RuleSet{}
for _, ruleSet := range ruleSets {
sets = append(sets, addExceptionsToRuleSet(ruleSet, exceptions))
}
return sets
}
func addExceptionsToRuleSet(ruleSet assertion.RuleSet, exceptions []RuleException) assertion.RuleSet {
rules := []assertion.Rule{}
for _, rule := range ruleSet.Rules {
for _, e := range exceptions {
if rule.ID == e.RuleID && resourceMatch(rule, e) && categoryMatch(rule, e) {
rule.Except = append(rule.Except, e.ResourceID)
}
}
rules = append(rules, rule)
}
ruleSet.Rules = rules
return ruleSet
}
func resourceMatch(rule assertion.Rule, exception RuleException) bool {
if assertion.SliceContains(rule.Resources, exception.ResourceType) || rule.Resource == exception.ResourceType {
return true
}
return false
}
func categoryMatch(rule assertion.Rule, exception RuleException) bool {
return rule.Category == exception.ResourceCategory || exception.ResourceCategory == "resources" || rule.Category == ""
}
func validateRules(filenames []string, w ReportWriter) (int, error) {
builtInRuleSet, err := loadBuiltInRuleSet("lint-rules.yml")
if err != nil {
return -1, err
}
ruleSets := []assertion.RuleSet{builtInRuleSet}
linterOptions := LinterOptions{
QueryExpression: "Violations[]",
}
return applyRules(ruleSets, filenames, linterOptions, w), nil
}
func loadRuleSets(args arrayFlags) ([]assertion.RuleSet, error) {
rulesFilenames := yamlFilesOnly(getFilenames(args))
ruleSets := []assertion.RuleSet{}
for _, rulesFilename := range rulesFilenames {
rulesContent, err := ioutil.ReadFile(rulesFilename)
if err != nil {
return ruleSets, err
}
ruleSet, err := assertion.ParseRules(string(rulesContent))
if err != nil {
return ruleSets, err
}
ruleSet.Source = rulesFilename
ruleSets = append(ruleSets, ruleSet)
}
return ruleSets, nil
}
func isYamlFile(filename string) bool {
configPatterns := []string{"*yml", "*.yaml"}
match, _ := assertion.ShouldIncludeFile(configPatterns, filename)
return match
}
func yamlFilesOnly(filenames []string) []string {
configFiles := []string{}
for _, filename := range filenames {
match := isYamlFile(filename)
if match {
configFiles = append(configFiles, filename)
}
}
return configFiles
}
// Takes a name of a rule YAML file or a directory containing YAML rules
// Returns a RuleSet of all rules in that file or directory
func loadBuiltInRuleSet(filename string) (assertion.RuleSet, error) {
ruleSet := assertion.RuleSet{}
box := packr.NewBox("./assets")
assertion.Debugf("Looking for file %v in Box: %v\n", filename, box)
var err error
if isYamlFile(filename) && box.Has(filename) {
ruleSet, err = addRuleSet(ruleSet, box, filename)
if err != nil {
assertion.Debugf("Failed to add RuleSet: %v\n", err)
return assertion.RuleSet{}, err // returns empty rule set
}
} else if strings.HasSuffix(filename, "/") {
filesInBox := box.List()
if len(filesInBox) > 0 {
// Get each file in that box
for _, fileInBox := range filesInBox {
// Check if file is YAML and starts with the folder name
assertion.Debugf("Box File: %v\n", fileInBox)
if isYamlFile(fileInBox) && strings.HasPrefix(fileInBox, filename) {
assertion.Debugf("Adding rule set: %v\n", fileInBox)
ruleSet, err = addRuleSet(ruleSet, box, fileInBox)
if err != nil {
assertion.Debugf("Failed to add RuleSet: %v\n", err)
return assertion.RuleSet{}, err // returns empty rule set
}
}
}
}
} else {
return assertion.RuleSet{}, errors.New("File or directory doesnt exist")
}
return ruleSet, nil
}
func addRuleSet(ruleSet assertion.RuleSet, box packr.Box, filename string) (assertion.RuleSet, error) {
// Get RuleSet from file
newRuleSet, err := getRuleSet(box, filename)
if err != nil {
assertion.Debugf("Failed to get RuleSet: %v\n", err)
return assertion.RuleSet{}, err // returns empty rule set
}
// Join with existing rule sets
ruleSet, err = assertion.JoinRuleSets(ruleSet, newRuleSet)
if err != nil {
assertion.Debugf("Failed to join RuleSets: %v\n", err)
return assertion.RuleSet{}, err // returns empty rule set
}
return ruleSet, nil
}
// Given a packr box and rule file in that box,
// build and return a RuleSet
func getRuleSet(box packr.Box, name string) (assertion.RuleSet, error) {
rulesContent, err := box.FindString(name)
if err != nil {
assertion.Debugf("Failed to find filename string in box: %v\n", err)
return assertion.RuleSet{}, err
}
ruleSet, err := assertion.ParseRules(string(rulesContent))
if err != nil {
assertion.Debugf("Failed to parse rules from file: %v\n", err)
return assertion.RuleSet{}, err
}
return ruleSet, nil
}
func applyRules(ruleSets []assertion.RuleSet, args arrayFlags, options LinterOptions, w ReportWriter) int {
report := assertion.ValidationReport{
Violations: []assertion.Violation{},
FilesScanned: []string{},
ResourcesScanned: []assertion.ScannedResource{},
}
tfParser := options.TerraformParser
filenames := excludeFilenames(getFilenames(args), options.ExcludePatterns)
vs := assertion.StandardValueSource{Variables: options.Variables}
for _, ruleSet := range ruleSets {
l, err := linter.NewLinter(ruleSet, vs, filenames, tfParser)
if err != nil {
fmt.Println(err)
return -1
}
if l != nil {
if options.SearchExpression != "" {
l.Search(ruleSet, options.SearchExpression, os.Stdout)
} else {
options := linter.Options{
Tags: options.Tags,
RuleIDs: options.RuleIDs,
IgnoreRuleIDs: options.IgnoreRuleIDs,
}
r, err := l.Validate(ruleSet, options)
if err != nil {
fmt.Println("Validate failed:", err)
}
report = linter.CombineValidationReports(report, r)
}
}
}
w.WriteReport(report, options)
return generateExitCode(report)
}
func printReport(w io.Writer, report assertion.ValidationReport, queryExpression string) error {
jsonData, err := json.MarshalIndent(report, "", " ")
if err != nil {
return err
}
if queryExpression != "" {
var data interface{}
err = yaml.Unmarshal(jsonData, &data)
if err != nil {
return err
}
v, err := assertion.SearchData(queryExpression, data)
if err != nil {
return err
}
s, err := assertion.JSONStringify(v)
if err == nil && s != "null" {
fmt.Fprintln(w, s)
}
} else {
fmt.Fprintln(w, string(jsonData))
}
return nil
}
type arrayFlags []string
func (i *arrayFlags) String() string {
if i != nil {
return strings.Join(*i, ",")
}
return ""
}
func (i *arrayFlags) Set(value string) error {
*i = append(*i, value)
return nil
}
func generateExitCode(report assertion.ValidationReport) int {
for _, v := range report.Violations {
if v.Status == "FAILURE" {
return -1
}
}
return 0
}
func loadFilenames(commandLineFilenames []string, profileFilenames []string) []string {
if len(commandLineFilenames) > 0 {
return commandLineFilenames
}
if len(profileFilenames) > 0 {
return profileFilenames
}
return []string{}
}
func defaultToCurrentDirectory(filenames []string) []string {
if len(filenames) == 0 {
return []string{"."}
}
return filenames
}
func excludeFilenames(filenames []string, excludePatterns []string) []string {
assertion.Debugf("Exclude patterns: %v\n", excludePatterns)
filteredFilenames := []string{}
for _, filename := range filenames {
if !excludeFilename(filename, excludePatterns) {
filteredFilenames = append(filteredFilenames, filename)
}
}
return filteredFilenames
}
func excludeFilename(filename string, excludePatterns []string) bool {
for _, pattern := range excludePatterns {
match, _ := filepath.Match(pattern, filename)
if match {
assertion.Debugf("Excluding file: %s using pattern: %s\n", filename, pattern)
return true
}
}
return false
}
func getFilenames(args []string) []string {
filenames := []string{}
for _, arg := range args {
if arg == "-" {
filenames = append(filenames, arg)
continue
}
fi, err := os.Stat(arg)
if err != nil {
// append as is, error reported later when file cannot be opened
filenames = append(filenames, arg)
continue
}
mode := fi.Mode()
if mode.IsDir() {
filenames = append(filenames, getFilesInDirectory(arg)...)
} else {
filenames = append(filenames, arg)
}
}
return filenames
}
func getFilesInDirectory(root string) []string {
directoryFiles := []string{}
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("Error processing %s: %s\n", path, err)
return err
}
if !info.IsDir() {
directoryFiles = append(directoryFiles, path)
}
return nil
})
if err != nil {
fmt.Printf("Error walking directory %s: %s\n", root, err)
}
return directoryFiles
}
================================================
FILE: cli/app_test.go
================================================
package main
import (
"bytes"
"testing"
"github.com/gobuffalo/packr"
"github.com/stelligent/config-lint/assertion"
"github.com/stelligent/config-lint/linter"
"github.com/stretchr/testify/assert"
)
func TestLoadTerraformRules(t *testing.T) {
_, err := loadBuiltInRuleSet("terraform/")
if err != nil {
t.Errorf("Cannot load built-in Terraform rules")
}
}
func TestLoadValidateRules(t *testing.T) {
_, err := loadBuiltInRuleSet("lint-rules.yml")
if err != nil {
t.Errorf("Cannot load built-in rules for -validate option")
}
}
func TestExcludeAll(t *testing.T) {
filenames := []string{"file1.tf", "file2.tf", "file3.tf"}
patterns := []string{"*.tf"}
filtered := excludeFilenames(filenames, patterns)
if len(filtered) != 0 {
t.Errorf("Expecting all files to be excluded, but files are %v", filtered)
}
}
func TestExcludeSubdirectory(t *testing.T) {
filenames := []string{"file1.tf", "foo/bar/secrets/database.yml"}
patterns := []string{"foo/bar/secrets/*"}
filtered := excludeFilenames(filenames, patterns)
if len(filtered) != 1 {
t.Errorf("Expecting secrets subdirectory to be excluded, but files are %v", filtered)
}
}
func TestExcludeOnePattern(t *testing.T) {
filenames := []string{"file1.tf", "file2.tf", "file3.tf"}
patterns := []string{"*1.tf"}
filtered := excludeFilenames(filenames, patterns)
if len(filtered) != 2 {
t.Errorf("Expecting one file to be excluded, but files are %v", filtered)
}
}
func TestExcludeMultiplePattern(t *testing.T) {
filenames := []string{"file1.tf", "file2.tf", "file3.tf"}
patterns := []string{"*1.tf", "*2.tf"}
filtered := excludeFilenames(filenames, patterns)
if len(filtered) != 1 {
t.Errorf("Expecting two files to be excluded, but files are %v", filtered)
}
}
func TestExcludeFrom(t *testing.T) {
excludeFromFilenames := []string{"./testdata/exclude-list"}
patterns, err := loadExcludePatterns([]string{}, excludeFromFilenames)
if err != nil {
t.Errorf("Expecting loadExcludePatterns returned error: %s", err.Error())
}
if len(patterns) != 2 {
t.Errorf("Expecting to load 2 patterns from excludeFromFilenames, not %v", patterns)
}
if patterns[0] != "*1.tf" {
t.Errorf("Expecting first pattern from file to be '*1.tf', not '%s'", patterns[0])
}
if patterns[1] != "*2.tf" {
t.Errorf("Expecting second pattern from file to be '*2.tf', not '%s'", patterns[1])
}
}
func TestProfileExceptions(t *testing.T) {
filenames := []string{"./testdata/terraform.yml"}
ruleSets, err := loadRuleSets(filenames)
if err != nil {
t.Errorf("Expecting loadRuleSets to not return error: %s", err.Error())
}
profileExceptions := []RuleException{
{
RuleID: "RULE_1",
ResourceCategory: "resource",
ResourceType: "aws_instance",
Comments: "Testing",
ResourceID: "my-special-resource",
},
}
ruleSets = addExceptions(ruleSets, profileExceptions)
ruleExceptions := ruleSets[0].Rules[0].Except
if len(ruleExceptions) != 1 {
t.Errorf("Expecting Rule.Except to have one ID: %v", ruleExceptions)
return
}
id := ruleExceptions[0]
if id != "my-special-resource" {
t.Errorf("Unexpected ResourceID found in Except: %s", id)
}
}
func TestBuiltRules(t *testing.T) {
ruleSet, err := loadBuiltInRuleSet("lint-rules.yml")
if err != nil {
t.Errorf("Expecting loadBuiltInRuleSet to not return error: %s", err.Error())
}
vs := assertion.StandardValueSource{}
// Get all rule files from the assets box
box := packr.NewBox("./assets")
allFilenames := box.List()
var filenames []string
for _, filename := range allFilenames {
if isYamlFile(filename) && !isTestCase(filename) {
filenames = append(filenames, "assets/"+filename)
}
}
l, err := linter.NewLinter(ruleSet, vs, filenames, "")
if err != nil {
t.Errorf("Expecting NewLinter to not return error: %s", err.Error())
}
options := linter.Options{}
report, err := l.Validate(ruleSet, options)
if err != nil {
t.Errorf("Expecting Validate to not return error: %s", err.Error())
}
if len(report.Violations) != 0 {
t.Errorf("Expecting Validate for built in rules to not report any violations: %v", report.Violations)
}
}
func TestPrintReport(t *testing.T) {
r := assertion.ValidationReport{}
var b bytes.Buffer
err := printReport(&b, r, "")
assert.Nil(t, err, "Expecting printReport to run without error")
assert.Contains(t, b.String(), "FilesScanned\": null")
assert.Contains(t, b.String(), "ResourcesScanned\": null")
assert.Contains(t, b.String(), "Violations\": null")
}
func TestPrintReportWithQueryString(t *testing.T) {
r := assertion.ValidationReport{
Violations: []assertion.Violation{
assertion.Violation{RuleMessage: "Houston, we have a problem"},
},
}
var b bytes.Buffer
err := printReport(&b, r, "Violations[]")
assert.Nil(t, err, "Expecting printReport to run without error")
assert.Contains(t, b.String(), "RuleMessage")
assert.NotContains(t, b.String(), "Violations")
assert.NotContains(t, b.String(), "FilesScanned")
assert.NotContains(t, b.String(), "ResourcesScanned")
}
type MockReportWriter struct {
Report assertion.ValidationReport
}
func (w MockReportWriter) WriteReport(r assertion.ValidationReport, o LinterOptions) {
w.Report = r
}
func TestApplyRules(t *testing.T) {
ruleSets := []assertion.RuleSet{
assertion.RuleSet{
Type: "JSON",
},
}
args := arrayFlags{}
options := LinterOptions{}
w := MockReportWriter{}
exitCode := applyRules(ruleSets, args, options, w)
assert.Equal(t, exitCode, 0, "Expecting applyRules to return 0")
assert.Empty(t, w.Report.Violations, "Expecting empty report")
}
func TestValidateRules(t *testing.T) {
filenames := []string{"./testdata/has-properties.yml"}
w := MockReportWriter{}
validateRules(filenames, w)
assert.Empty(t, w.Report.Violations, "Expecting empty report for validateRules")
}
func TestResourceMatch(t *testing.T) {
testRule := []assertion.Rule{
{
ID: "RULE_1",
Category: "resource",
Resources: []string{"aws_instance", "aws_s3_bucket"},
},
{
ID: "RULE_2",
Category: "resource",
Resource: "aws_s3_bucket",
},
}
profileExceptions := []RuleException{
{
RuleID: "RULE_1",
ResourceCategory: "resource",
ResourceType: "aws_instance",
Comments: "Testing",
ResourceID: "my-special-resource",
},
{
RuleID: "RULE_2",
ResourceCategory: "resources",
ResourceType: "aws_s3_bucket",
Comments: "Testing",
ResourceID: "my-special-bucket",
},
{
RuleID: "RULE_2",
ResourceCategory: "resources",
ResourceType: "aws_vpc",
Comments: "Should not match",
ResourceID: "my-vpc",
},
}
assert.True(t, resourceMatch(testRule[0], profileExceptions[0]), "Expecting exception resource to be found in rule resources")
assert.True(t, resourceMatch(testRule[1], profileExceptions[1]), "Expecting one to one match with exception resource and rule resource")
assert.False(t, resourceMatch(testRule[1], profileExceptions[2]), "Expecting rule and exception to not match")
}
func TestLoadRuleSetsBadFilename(t *testing.T) {
args := []string{"no-such-file.yml"}
_, err := loadRuleSets(args)
assert.NotNil(t, err, "LoadRuleSet with bad filename should return an error")
}
func TestLoadRuleSetsParseErrors(t *testing.T) {
args := []string{"./testdata/syntax-errors.yml"}
_, err := loadRuleSets(args)
assert.NotNil(t, err, "Expecting rules file with syntax errors to fail")
if err != nil {
assert.Contains(t, err.Error(), "error unmarshaling JSON")
}
}
func TestStdinFilename(t *testing.T) {
filenames := getFilenames([]string{"-"})
assert.Len(t, filenames, 1, "getFilenames should file 1 file")
assert.Equal(t, filenames[0], "-", "getFilenames should allow - for stdin")
}
func TestGetFilenamesUsingDirectory(t *testing.T) {
filenames := getFilenames([]string{"./testdata/dirtest"})
assert.Len(t, filenames, 2)
assert.Equal(t, "testdata/dirtest/a.yml", filenames[0])
assert.Equal(t, "testdata/dirtest/b.yml", filenames[1])
}
func TestLoadFilenamesFromCommandLine(t *testing.T) {
commandLineFilenames := []string{"command.yml"}
profileFilenames := []string{"default.yml"}
result := loadFilenames(commandLineFilenames, profileFilenames)
assert.Equal(t, result, commandLineFilenames)
}
func TestLoadFilenamesFromProfile(t *testing.T) {
commandLineFilenames := []string{}
profileFilenames := []string{"default.yml"}
result := loadFilenames(commandLineFilenames, profileFilenames)
assert.Equal(t, result, profileFilenames)
}
func TestArrayFlags(t *testing.T) {
var f arrayFlags
assert.Equal(t, "", f.String(), "Default arrayFlags should return empty string")
f.Set("first")
f.Set("second")
assert.Equal(t, arrayFlags{"first", "second"}, f, "Expecting arrayFlags to have two elements")
}
func TestLoadBuiltInRuleSetMissing(t *testing.T) {
_, err := loadBuiltInRuleSet("missing.yml")
assert.Contains(t, err.Error(), "File or directory doesnt exist", "loadBuiltInRuleSet should fail for missing file")
}
================================================
FILE: cli/assets/lint-rules.yml
================================================
---
version: 1
description: Rules for config-lint
type: LintRules
files:
- "*.yml"
rules:
- id: VALID_TYPE
message: Not a valid linter type
resource: LintRuleSet
severity: FAILURE
assertions:
- key: type
op: in
value: Terraform,Terraform12,Kubernetes,LintRules,YAML,JSON,CSV
- id: VALID_VERSION
message: RuleSet must have a supported version
resource: LintRuleSet
severity: WARNING
assertions:
- key: version
op: eq
value: 1
- id: HAS_RULES
message: RuleSet needs at least one rule
resource: LintRuleSet
severity: WARNING
assertions:
- key: rules
op: not-empty
- id: YAML_RULES_HAVE_RESOURCES_SECTION
message: RuleSet for YAML required resources section
resource: LintRuleSet
severity: FAILURE
conditions:
- key: type
op: eq
value: YAML
assertions:
- key: resources
op: present
- id: EVERY_RULE_HAS_ID
message: Event rule in rule set must have an id
resource: LintRuleSet
severity: FAILURE
assertions:
- every:
key: rules
expressions:
- key: id
op: present
- id: ID_PRESENT
message: Rule must have an ID
resource: LintRule
severity: FAILURE
assertions:
- key: id
op: present
- id: RESOURCE_PRESENT
message: Rule must have a resource, resources or except_resources attribute
severity: FAILURE
resource: LintRule
assertions:
- or:
- key: resource
op: present
- key: resources
op: present
- key: except_resources
op: present
tags:
- resource
- id: ASSERTIONS_OR_INVOKE
message: Rule must have assertions or invoke
resource: LintRule
severity: FAILURE
assertions:
- or:
- key: assertions
op: present
- key: invoke
op: present
- id: VALID_EXPRESSION
message: "These are mutually exclusive in the same expression: key,or,xor,and,not,every,some,none"
resource: LintRule
severity: FAILURE
assertions:
- every:
key: "assertions[]"
expressions:
- xor:
- key: "@"
op: has-properties
value: key,op
- key: "@"
op: has-properties
value: or
- key: "@"
op: has-properties
value: xor
- key: "@"
op: has-properties
value: and
- key: "@"
op: has-properties
value: not
- key: "@"
op: has-properties
value: every
- key: "@"
op: has-properties
value: some
- key: "@"
op: has-properties
value: none
- key: "@"
op: has-properties
value: exactly-one
================================================
FILE: cli/assets/terraform/aws/api_gateway/api_gateway_domain_name/security_policy/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: API_GW_DOMAIN_SECURITY_POLICY_TLS1_2
message: API Gateway domain name must use TLS 1.2
resource: aws_api_gateway_domain_name
severity: FAILURE
assertions:
- key: security_policy
op: eq
value: "TLS_1_2"
tags:
- api_gateway
================================================
FILE: cli/assets/terraform/aws/api_gateway/api_gateway_domain_name/security_policy/tests/terraform12/security_policy.tf
================================================
# Test that an api_gateway_domain_name is using TLS 1.2
# https://www.terraform.io/docs/providers/aws/r/api_gateway_domain_name.html#security_policy
provider "aws" {
region = "us-east-1"
}
# PASS: security_policy is set to TLS 1.2
resource "aws_api_gateway_domain_name" "api_gw_domain_using_tls1_2" {
domain_name = "api.example.com"
endpoint_configuration {
types = ["REGIONAL"]
}
security_policy = "TLS_1_2"
}
# FAIL: security_policy is not defined
resource "aws_api_gateway_domain_name" "api_gw_security_policy_not_set" {
domain_name = "api.example.com"
endpoint_configuration {
types = ["REGIONAL"]
}
}
# FAIL: security_policy is set to TLS 1.0
resource "aws_api_gateway_domain_name" "api_gw_domain_using_tls1_0" {
domain_name = "api.example.com"
endpoint_configuration {
types = ["REGIONAL"]
}
security_policy = "TLS_1_0"
}
================================================
FILE: cli/assets/terraform/aws/api_gateway/api_gateway_domain_name/security_policy/tests/test.yml
================================================
---
version: 1
description: Terraform 11 and 12 tests
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: API_GW_DOMAIN_SECURITY_POLICY_TLS1_2
warnings: 0
failures: 2
tags:
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/batch/batch_job_definition/aws_secrets/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: BATCH_JOB_AWS_ENVIRONMENT_SECRETS
message: Environment for batch jobs should not include AWS secrets
resource: aws_batch_job_definition
severity: FAILURE
# This rule fails if it finds a regex match for either the Access Key ID and/or the Secret Access Key
assertions:
- not:
- some:
key: "container_properties.environment[].value"
expressions:
# Check if the string starts with any known 4 character ACCESS_KEY sequence
# and is 20 capital alpha-numeric characters long in total
- key: "@"
op: regex
value: "^(A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}$"
- some:
key: "container_properties.environment[].value"
expressions:
- and:
# Check if the string is exactly 40 characters long
- key: "@"
op: regex
value: "^.{40}$"
# Check if the string contains only alpha-numeric-slash-plus characters with at least 1 / or +
- key: "@"
op: regex
value: "^[a-zA-Z0-9/+]+[/+]+[a-zA-Z0-9/+]+$"
tags:
- batch
================================================
FILE: cli/assets/terraform/aws/batch/batch_job_definition/aws_secrets/tests/terraform12/aws_secrets.tf
================================================
# Test that AWS secrets are not being used in batch environment variables
# https://www.terraform.io/docs/providers/aws/r/batch_job_definition.html#container_properties
# Reference API for container_properties spec: https://docs.aws.amazon.com/batch/latest/APIReference/API_RegisterJobDefinition.html
provider "aws" {
region = "us-east-1"
}
# PASS: AWS secrets are not used in the env vars
resource "aws_batch_job_definition" "batch_job_without_secrets" {
name = "tf_test_batch_job_definition"
type = "container"
container_properties = <<CONTAINER_PROPERTIES
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"},
{"name": "VARNAMETWO", "value": "VARVALTWO"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
CONTAINER_PROPERTIES
}
# FAIL: AWS secret key in env vars
resource "aws_batch_job_definition" "batch_job_with_secret_key" {
name = "tf_test_batch_job_definition"
type = "container"
container_properties = <<CONTAINER_PROPERTIES
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"},
{"name": "VARNAMESECRET", "value": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
CONTAINER_PROPERTIES
}
# FAIL: AWS access key ID in env vars
resource "aws_batch_job_definition" "batch_job_with_secret_access_id" {
name = "tf_test_batch_job_definition"
type = "container"
container_properties = <<CONTAINER_PROPERTIES
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"},
{"name": "VARNAMESECRET", "value": "AKIAIOSFODNN7EXAMPLE"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
CONTAINER_PROPERTIES
}
================================================
FILE: cli/assets/terraform/aws/batch/batch_job_definition/aws_secrets/tests/test.yml
================================================
---
version: 1
description: Terraform 12 test
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: BATCH_JOB_AWS_ENVIRONMENT_SECRETS
warnings: 0
failures: 2
tags:
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: BATCH_DEFINITION_PRIVILEGED
message: Batch Job Definition Container Properties should not have Privileged set to true
resource: aws_batch_job_definition
severity: WARNING
assertions:
- not:
- key: container_properties.privileged
op: is-true
tags:
- batch
================================================
FILE: cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/tests/terraform11/container_properties_privileged.tf
================================================
# Pass
resource "aws_batch_job_definition" "container_properties_privileged_not_set" {
name = "foo"
type = "container"
container_properties = <<EOF
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
EOF
}
# Pass
resource "aws_batch_job_definition" "container_properties_privileged_set_to_false" {
name = "foo"
type = "container"
container_properties = <<EOF
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"privileged": "false",
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
EOF
}
# Warn
resource "aws_batch_job_definition" "container_properties_privileged_set_to_true" {
name = "foo"
type = "container"
container_properties = <<EOF
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"privileged": "true",
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
EOF
}
================================================
FILE: cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/tests/terraform12/container_properties_privileged.tf
================================================
# Pass
resource "aws_batch_job_definition" "container_properties_privileged_not_set" {
name = "foo"
type = "container"
container_properties = <<EOF
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
EOF
}
# Pass
resource "aws_batch_job_definition" "container_properties_privileged_set_to_false" {
name = "foo"
type = "container"
container_properties = <<EOF
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"privileged": "false",
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
EOF
}
# Warn
resource "aws_batch_job_definition" "container_properties_privileged_set_to_true" {
name = "foo"
type = "container"
container_properties = <<EOF
{
"command": ["ls", "-la"],
"image": "busybox",
"memory": 1024,
"vcpus": 1,
"privileged": "true",
"volumes": [
{
"host": {
"sourcePath": "/tmp"
},
"name": "tmp"
}
],
"environment": [
{"name": "VARNAME", "value": "VARVAL"}
],
"mountPoints": [
{
"sourceVolume": "tmp",
"containerPath": "/tmp",
"readOnly": false
}
],
"ulimits": [
{
"hardLimit": 1024,
"name": "nofile",
"softLimit": 1024
}
]
}
EOF
}
================================================
FILE: cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/tests/test.yml
================================================
---
version: 1
description: Terraform 11 and 12 tests
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: BATCH_DEFINITION_PRIVILEGED
warnings: 1
failures: 0
tags:
- "terraform11"
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: CLOUDFRONT_DISTRIBUTION_ORIGIN_POLICY
message: CloudFront Distribution should use OAI or origin_protocol_policy should be https-only
resource: aws_cloudfront_distribution
severity: FAILURE
assertions:
- or:
- key: "origin[].s3_origin_config[].origin_access_identity"
op: present
- or:
- key: "origin[].custom_origin_config"
op: absent
- key: "origin[].custom_origin_config[].origin_protocol_policy"
op: contains
value: https-only
tags:
- cloudfront
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/tests/terraform11/custom_origin_config.tf
================================================
# Pass
resource "aws_cloudfront_distribution" "custom_origin_config_not_set" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Pass
resource "aws_cloudfront_distribution" "custom_origin_config_set_to_https-only" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "custom_origin_config_set_to_http-only" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "custom_origin_config_set_to_match-viewer" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "match-viewer"
origin_ssl_protocols = ["TLSv1.2"]
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/tests/terraform12/custom_origin_config.tf
================================================
# Test that a cloudfront_distribution resource is using OAI or origin_protocol_policy is using https-only
# https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#origin_access_identity
# https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#origin_protocol_policy
## Setup Helper
variable "test_domain_s3_location" {
default = "http://foo.s3-website-us-east-1.amazonaws.com"
}
variable "test_origin_id" {
default = "fooOrigin"
}
variable "test_logging_bucket" {
default = "foologs.s3.amazonaws.com"
}
variable "test_logging_prefix" {
default = "aws_cloudfront_distribution"
}
# PASS: OIA is used
resource "aws_cloudfront_distribution" "custom_origin_config_not_set" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# PASS: origin_protocol_policy is https-only
resource "aws_cloudfront_distribution" "custom_origin_config_set_to_https-only" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# FAIL: origin_protocol_policy is not https-only
resource "aws_cloudfront_distribution" "custom_origin_config_set_to_http-only" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "http-only"
origin_ssl_protocols = ["TLSv1.2"]
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# FAIL: origin_protocol_policy is not https-only
resource "aws_cloudfront_distribution" "custom_origin_config_set_to_match-viewer" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "match-viewer"
origin_ssl_protocols = ["TLSv1.2"]
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/tests/test.yml
================================================
---
version: 1
description: Terraform 11 and 12 tests
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: CLOUDFRONT_DISTRIBUTION_ORIGIN_POLICY
warnings: 0
failures: 2
tags:
- "terraform11"
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: CLOUDFRONT_DISTRIBUTION_LOGGING
message: CloudFront Distribution must configure logging
resource: aws_cloudfront_distribution
severity: FAILURE
assertions:
- key: logging_config
op: present
tags:
- cloudfront
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/tests/terraform11/logging_config.tf
================================================
# Pass
resource "aws_cloudfront_distribution" "logging_enabled" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "logging_disabled" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/tests/terraform12/logging_config.tf
================================================
## Setup Helper
variable "test_domain_s3_location" {
default = "http://foo.s3-website-us-east-1.amazonaws.com"
}
variable "test_origin_id" {
default = "fooOrigin"
}
variable "test_logging_bucket" {
default = "foologs.s3.amazonaws.com"
}
variable "test_logging_prefix" {
default = "aws_cloudfront_distribution"
}
# Pass
resource "aws_cloudfront_distribution" "logging_enabled" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "logging_disabled" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/tests/test.yml
================================================
---
version: 1
description: Terraform 11 and 12 tests
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: CLOUDFRONT_DISTRIBUTION_LOGGING
warnings: 0
failures: 1
tags:
- "terraform11"
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/minimum_ssl_protocol/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: CLOUDFRONT_MINIMUM_SSL
message: CloudFront Distribution must use TLS 1.2
resource: aws_cloudfront_distribution
severity: FAILURE
assertions:
- key: viewer_certificate[].cloudfront_default_certificate | [0]
op: is-false
- key: viewer_certificate[].minimum_protocol_version | [0]
op: starts-with
value: "TLSv1.2"
tags:
- cloudfront
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/minimum_ssl_protocol/tests/terraform12/minimum_ssl_protocol.tf
================================================
# Test that a CloudFront distribution viewer_certificate is using TLS 1.2
# https://www.terraform.io/docs/providers/aws/r/cloudfront_distribution.html#viewer-certificate-arguments
provider "aws" {
region = "us-east-1"
}
# PASS
resource "aws_cloudfront_distribution" "cf_using_tls_1_2" {
origin {
domain_name = "example.com"
origin_id = "s3ExampleOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
enabled = true
is_ipv6_enabled = true
comment = "Some comment"
default_root_object = "index.html"
logging_config {
include_cookies = false
bucket = "mylogs.s3.amazonaws.com"
prefix = "myprefix"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "s3ExampleOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
}
}
viewer_certificate {
cloudfront_default_certificate = false
minimum_protocol_version = "TLSv1.2_2018"
}
}
# FAIL: distribution has cloudfront_default_certificate enabled
resource "aws_cloudfront_distribution" "cf_using_tls_1_2_with_default_cert" {
origin {
domain_name = "example.com"
origin_id = "s3ExampleOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
enabled = true
is_ipv6_enabled = true
comment = "Some comment"
default_root_object = "index.html"
logging_config {
include_cookies = false
bucket = "mylogs.s3.amazonaws.com"
prefix = "myprefix"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "s3ExampleOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
}
}
viewer_certificate {
cloudfront_default_certificate = true
minimum_protocol_version = "TLSv1.2_2018"
}
}
# FAIL: distribution is not using TLS 1.2
resource "aws_cloudfront_distribution" "cf_using_not_using_tls_1_2" {
origin {
domain_name = "example.com"
origin_id = "s3ExampleOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
enabled = true
is_ipv6_enabled = true
comment = "Some comment"
default_root_object = "index.html"
logging_config {
include_cookies = false
bucket = "mylogs.s3.amazonaws.com"
prefix = "myprefix"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "s3ExampleOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
}
}
viewer_certificate {
cloudfront_default_certificate = false
minimum_protocol_version = "TLSv1"
}
}
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/minimum_ssl_protocol/tests/test.yml
================================================
---
version: 1
description: Terraform 11 and 12 tests
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: CLOUDFRONT_MINIMUM_SSL
warnings: 0
failures: 2
tags:
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: CLOUDFRONT_DISTRIBUTION_PROTOCOL
message: CloudFront Distribution should not allow all protocols
resource: aws_cloudfront_distribution
severity: FAILURE
assertions:
- key: "default_cache_behavior[].viewer_protocol_policy"
op: does-not-contain
value: allow-all
- key: "ordered_cache_behavior[].viewer_protocol_policy"
op: does-not-contain
value: allow-all
tags:
- cloudfront
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/tests/terraform11/viewer_protocol_policy.tf
================================================
# Pass
resource "aws_cloudfront_distribution" "default_cache_behavior_viewer_protocol_policy_set_to_https-only" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "https-only"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Pass
resource "aws_cloudfront_distribution" "default_cache_behavior_viewer_protocol_policy_set_to_redirect-to-https" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "default_cache_behavior_viewer_protocol_policy_set_to_allow-all" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Pass
resource "aws_cloudfront_distribution" "ordered_cache_behavior_viewer_protocol_policy_set_to_https-only" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
ordered_cache_behavior {
path_pattern = "/foo/bar/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
headers = ["Origin"]
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "https-only"
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Pass
resource "aws_cloudfront_distribution" "ordered_cache_behavior_viewer_protocol_policy_set_to_redirect-to-https" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
ordered_cache_behavior {
path_pattern = "/foo/bar/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
headers = ["Origin"]
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "redirect-to-https"
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "ordered_cache_behavior_viewer_protocol_policy_set_to_allow-all" {
enabled = true
origin {
domain_name = "http://foo.s3-website-us-east-1.amazonaws.com"
origin_id = "fooOrigin"
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = "foologs.s3.amazonaws.com"
prefix = "aws_cloudfront_distribution"
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
ordered_cache_behavior {
path_pattern = "/foo/bar/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
headers = ["Origin"]
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "allow-all"
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/tests/terraform12/viewer_protocol_policy.tf
================================================
## Setup Helper
variable "test_domain_s3_location" {
default = "http://foo.s3-website-us-east-1.amazonaws.com"
}
variable "test_origin_id" {
default = "fooOrigin"
}
variable "test_logging_bucket" {
default = "foologs.s3.amazonaws.com"
}
variable "test_logging_prefix" {
default = "aws_cloudfront_distribution"
}
# Pass
resource "aws_cloudfront_distribution" "default_cache_behavior_viewer_protocol_policy_set_to_https-only" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "https-only"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Pass
resource "aws_cloudfront_distribution" "default_cache_behavior_viewer_protocol_policy_set_to_redirect-to-https" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "default_cache_behavior_viewer_protocol_policy_set_to_allow-all" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "allow-all"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Pass
resource "aws_cloudfront_distribution" "ordered_cache_behavior_viewer_protocol_policy_set_to_https-only" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
ordered_cache_behavior {
path_pattern = "/foo/bar/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
headers = ["Origin"]
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "https-only"
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Pass
resource "aws_cloudfront_distribution" "ordered_cache_behavior_viewer_protocol_policy_set_to_redirect-to-https" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
ordered_cache_behavior {
path_pattern = "/foo/bar/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
headers = ["Origin"]
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "redirect-to-https"
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
# Fail
resource "aws_cloudfront_distribution" "ordered_cache_behavior_viewer_protocol_policy_set_to_allow-all" {
enabled = true
origin {
domain_name = var.test_domain_s3_location
origin_id = var.test_origin_id
s3_origin_config {
origin_access_identity = "origin-access-identity/cloudfront/ABCDEFG1234567"
}
}
logging_config {
include_cookies = false
bucket = var.test_logging_bucket
prefix = var.test_logging_prefix
}
default_cache_behavior {
allowed_methods = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
cached_methods = ["GET", "HEAD"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
cookies {
forward = "none"
}
}
viewer_protocol_policy = "redirect-to-https"
min_ttl = 0
default_ttl = 3600
max_ttl = 86400
}
restrictions {
geo_restriction {
restriction_type = "whitelist"
locations = ["US", "CA", "GB", "DE"]
}
}
ordered_cache_behavior {
path_pattern = "/foo/bar/*"
allowed_methods = ["GET", "HEAD", "OPTIONS"]
cached_methods = ["GET", "HEAD", "OPTIONS"]
target_origin_id = "fooOrigin"
forwarded_values {
query_string = false
headers = ["Origin"]
cookies {
forward = "none"
}
}
min_ttl = 0
default_ttl = 86400
max_ttl = 31536000
compress = true
viewer_protocol_policy = "allow-all"
}
viewer_certificate {
cloudfront_default_certificate = true
}
}
================================================
FILE: cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/tests/test.yml
================================================
---
version: 1
description: Terraform 11 and 12 tests
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: CLOUDFRONT_DISTRIBUTION_PROTOCOL
warnings: 0
failures: 2
tags:
- "terraform11"
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: CLOUDTRAIL_ENCRYPTION
message: CloudTrail should specify a non-default KMS Key
resource: aws_cloudtrail
severity: WARNING
assertions:
- key: kms_key_id
op: present
tags:
- cloudtrail
================================================
FILE: cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/tests/terraform11/kms_key_id.tf
================================================
## Setup Helper
resource "aws_kms_key" "test_key" {
enable_key_rotation = true
}
resource "aws_s3_bucket" "test_bucket" {
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = "${aws_kms_key.test_key.arn}"
sse_algorithm = "aws:kms"
}
}
}
}
# Pass
resource "aws_cloudtrail" "kms_key_id_is_set" {
name = "foo"
s3_bucket_name = "${aws_s3_bucket.test_bucket.bucket}"
kms_key_id = "${aws_kms_key.test_key.arn}"
}
# Warn
resource "aws_cloudtrail" "kms_key_id_is_not_set" {
name = "foo"
s3_bucket_name = "${aws_s3_bucket.test_bucket.bucket}"
}
================================================
FILE: cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/tests/terraform12/kms_key_id.tf
================================================
## Setup Helper
variable "test_cloudtrail_name" {
default = "foo"
}
resource "aws_kms_key" "test_key" {
enable_key_rotation = true
}
resource "aws_s3_bucket" "test_bucket" {
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = "${aws_kms_key.test_key.arn}"
sse_algorithm = "aws:kms"
}
}
}
}
# Pass
resource "aws_cloudtrail" "kms_key_id_is_set" {
name = var.test_cloudtrail_name
s3_bucket_name = aws_s3_bucket.test_bucket.bucket
kms_key_id = aws_kms_key.test_key.arn
}
# Warn
resource "aws_cloudtrail" "kms_key_id_is_not_set" {
name = var.test_cloudtrail_name
s3_bucket_name = aws_s3_bucket.test_bucket.bucket
}
================================================
FILE: cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/tests/test.yml
================================================
---
version: 1
description: Terraform 11 and 12 tests
type: Terraform
files:
- "*.tf"
- "*.tfvars"
tests:
-
ruleId: CLOUDTRAIL_ENCRYPTION
warnings: 1
failures: 0
tags:
- "terraform11"
- "terraform12"
================================================
FILE: cli/assets/terraform/aws/cloudwatch/cloudwatch_log_destination_policy/wildcard_principal/rule.yml
================================================
---
version: 1
description: Terraform rules
type: Terraform
files:
- "*.tf"
- "*.tfvars"
rules:
- id: CLOUDWATCH_WILDCARD_PRINCIPAL
message: Cloudwatch destination policy allow policy should not use a wildcard princpal
resource: aws_cloudwatch_log_destination_policy
severity: FAILURE
assertions:
- none:
key: access_policy.Statement
expressions:
- key: Effect
op: eq
value: Allow
- key: Principal
op: contains
value: "*"
tags:
- cloudwatch
- policy
================================================
FILE: cli/assets/terraform/aws/cloudwatch/cloudwatch_log_destination_policy/wildcard_principal/tests/terraform12/wildcard_principal.tf
================================================
# Test that CloudWatch log destination policy is not using a wildcard principal
# https://www.terraform.io/docs/providers/aws/r/cloudwatch_log_destination_policy.html#access_policy
provider "aws" {
region = "us-east-1"
}
# PASS: Allow statement does not use a wildcard principal
resource "aws_cloudwatch_log_destination_policy" "cw_destination_no_wildcard" {
destination_name = "cloudwatch_destination"
access_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "cloudwatch:*",
"Principal": {
"AWS": [
"arn:aws:iam::1234567890:user/foo"
]
},
"Effect": "Allow",
"Resource": "arn:aws:logs:us-west-1:123456789012:log-group:/mystack-testgroup-12ABC1AB12A1:*"
}
]
}
EOF
}
# PASS: Deny statement does not use a wildcard principal
resource "aws_cloudwatch_log_destination_policy" "cw_destination_deny_no_wildcard" {
destination_name = "cloudwatch_destination"
access_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "cloudwatch:*",
"Principal": {
"AWS": [
"arn:aws:iam::1234567890:user/foo"
]
},
"Effect": "Deny",
"Resource": "arn:aws:logs:us-west-1:123456789012:log-group:/mystack-testgroup-12ABC1AB12A1:*"
}
]
}
EOF
}
# PASS: Deny statement uses a wildcard principal
resource "aws_cloudwatch_log_destination_policy" "cw_destination_deny_with_wildcard" {
destination_name = "cloudwatch_destination"
access_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "cloudwatch:*",
"Principal": {
"AWS": [
"arn:aws:iam::1234567890:user/*"
]
},
"Effect": "Deny",
"Resource": "arn:aws:logs:us-west-1:123456789012:log-group:/mystack-testgroup-12ABC1AB12A1:*"
}
]
}
EOF
}
# FAIL: Allow statement uses a wildcard principal
resource "aws_cloudwatch_log_destination_policy" "cw_destination_allow_with_wildcard" {
destination_name = "cloudwatch_destination"
access_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "cloudwatch:*",
"Principal": {
"AWS": [
"arn:aws:iam::1234567890:user/*"
]
},
gitextract_py6281yd/
├── .devcontainer/
│ ├── Dockerfile
│ ├── build/
│ │ ├── Dockerfile
│ │ └── dockerhub.sh
│ └── devcontainer.json
├── .dockerhub/
│ └── Dockerfile
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── build_and_deploy.yml
│ ├── bump_version.yml
│ └── vscode_remote_development.yml
├── .gitignore
├── .goreleaser.yml
├── CONTRIBUTING.md
├── LICENSE.md
├── Makefile
├── README.md
├── assertion/
│ ├── compare.go
│ ├── compare_test.go
│ ├── contains.go
│ ├── contains_test.go
│ ├── expression.go
│ ├── expression_test.go
│ ├── has_properties.go
│ ├── helper_test.go
│ ├── invoke.go
│ ├── invoke_test.go
│ ├── ip_operations.go
│ ├── ip_operations_test.go
│ ├── log.go
│ ├── match.go
│ ├── match_test.go
│ ├── rules.go
│ ├── rules_test.go
│ ├── search.go
│ ├── testdata/
│ │ ├── collection-assertions.yaml
│ │ ├── conditions.yaml
│ │ ├── default-severity.yaml
│ │ └── has-properties.yaml
│ ├── types.go
│ ├── util.go
│ ├── util_test.go
│ ├── value.go
│ └── value_test.go
├── cli/
│ ├── app.go
│ ├── app_test.go
│ ├── assets/
│ │ ├── lint-rules.yml
│ │ └── terraform/
│ │ └── aws/
│ │ ├── api_gateway/
│ │ │ └── api_gateway_domain_name/
│ │ │ └── security_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── security_policy.tf
│ │ │ └── test.yml
│ │ ├── batch/
│ │ │ └── batch_job_definition/
│ │ │ ├── aws_secrets/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── aws_secrets.tf
│ │ │ │ └── test.yml
│ │ │ └── container_properties_privileged/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── container_properties_privileged.tf
│ │ │ ├── terraform12/
│ │ │ │ └── container_properties_privileged.tf
│ │ │ └── test.yml
│ │ ├── cloudfront/
│ │ │ └── cloudfront_distribution/
│ │ │ ├── custom_origin_config/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── custom_origin_config.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── custom_origin_config.tf
│ │ │ │ └── test.yml
│ │ │ ├── logging_config/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── logging_config.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── logging_config.tf
│ │ │ │ └── test.yml
│ │ │ ├── minimum_ssl_protocol/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── minimum_ssl_protocol.tf
│ │ │ │ └── test.yml
│ │ │ └── viewer_protocol_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── viewer_protocol_policy.tf
│ │ │ ├── terraform12/
│ │ │ │ └── viewer_protocol_policy.tf
│ │ │ └── test.yml
│ │ ├── cloudtrail/
│ │ │ └── cloudtrail/
│ │ │ └── kms_key_id/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key_id.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key_id.tf
│ │ │ └── test.yml
│ │ ├── cloudwatch/
│ │ │ └── cloudwatch_log_destination_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── codebuild/
│ │ │ └── codebuild_project/
│ │ │ ├── artifact_encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── artifact_encryption.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── artifact_encryption.tf
│ │ │ │ └── test.yml
│ │ │ └── project_encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── project_encryption.tf
│ │ │ ├── terraform12/
│ │ │ │ └── project_encryption.tf
│ │ │ └── test.yml
│ │ ├── codepipeline/
│ │ │ └── codepipeline/
│ │ │ └── encryption_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encryption_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encryption_key.tf
│ │ │ └── test.yml
│ │ ├── dms/
│ │ │ └── dms_endpoint/
│ │ │ └── endpoint_kms_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key.tf
│ │ │ └── test.yml
│ │ ├── documentdb/
│ │ │ └── docdb_cluster/
│ │ │ ├── audit_logs/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── audit_logs.tf
│ │ │ │ └── test.yml
│ │ │ └── storage_encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── storage_encryption.tf
│ │ │ └── test.yml
│ │ ├── ec2/
│ │ │ ├── ami/
│ │ │ │ └── ebs_block_device_encrypted/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── ebs_block_device_encrypted.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── ebs_block_device_encrypted.tf
│ │ │ │ └── test.yml
│ │ │ ├── ami_copy/
│ │ │ │ └── encrypted/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ └── test.yml
│ │ │ ├── ebs_volume/
│ │ │ │ └── encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encrypted.tf
│ │ │ │ └── test.yml
│ │ │ └── instance/
│ │ │ └── ebs_block_device_encrypted/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── ebs_block_device_encrypted.tf
│ │ │ └── test.yml
│ │ ├── ecr/
│ │ │ └── ecr_repository_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── ecs/
│ │ │ └── ecs_task_definition/
│ │ │ └── task_definition_secrets/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── secrets.tf
│ │ │ ├── terraform12/
│ │ │ │ └── secrets.tf
│ │ │ └── test.yml
│ │ ├── efs/
│ │ │ └── efs_file_system/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encrypted.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encrypted.tf
│ │ │ └── test.yml
│ │ ├── elastic_load_balancing/
│ │ │ ├── alb/
│ │ │ │ └── alb_access_logs_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ └── test.yml
│ │ │ ├── alb_listener/
│ │ │ │ └── alb_listener_https/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── https.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── https.tf
│ │ │ │ └── test.yml
│ │ │ ├── elb/
│ │ │ │ └── access_logs_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ └── test.yml
│ │ │ ├── lb/
│ │ │ │ └── access_logs_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── access_logs_enabled.tf
│ │ │ │ └── test.yml
│ │ │ └── lb_listener/
│ │ │ ├── listener_https/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── https.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── https.tf
│ │ │ │ └── test.yml
│ │ │ └── listener_ssl_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── ssl_policy.tf
│ │ │ ├── terraform12/
│ │ │ │ └── ssl_policy.tf
│ │ │ └── test.yml
│ │ ├── elasticache/
│ │ │ └── elasticache_replication_group/
│ │ │ ├── encryption_at_rest/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption_at_rest.tf
│ │ │ │ └── test.yml
│ │ │ └── encryption_in_transit/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encryption_in_transit.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encryption_in_transit.tf
│ │ │ └── test.yml
│ │ ├── elasticsearch/
│ │ │ ├── elasticsearch_domain/
│ │ │ │ ├── encryption_at_rest/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encryption_at_rest.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── encryption_node_to_node/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encryption_node_to_node.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── vpc_subnets/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── elasticsearch_vpc.tf
│ │ │ │ └── test.yml
│ │ │ └── shared/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ ├── elasticsearch_domain_policy_wildcard_principal.tf
│ │ │ │ └── elasticsearch_domain_wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── elastictranscoder/
│ │ │ └── elastictranscoder_pipeline/
│ │ │ └── require_encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── require_encryption.tf
│ │ │ └── test.yml
│ │ ├── emr/
│ │ │ └── emr_cluster/
│ │ │ └── logging/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── logging.tf
│ │ │ ├── terraform12/
│ │ │ │ └── logging.tf
│ │ │ └── test.yml
│ │ ├── glue/
│ │ │ └── glue_connection/
│ │ │ └── connection_properties/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── connection_properties.tf
│ │ │ └── test.yml
│ │ ├── iam/
│ │ │ ├── iam_group_membership/
│ │ │ │ └── group_and_users/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── group_and_users.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── group_and_users.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_policy/
│ │ │ │ ├── policy_action_wildcard/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── policy_notaction/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── policy_notresource/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── policy_resource_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_role/
│ │ │ │ ├── assume_role_policy_action_wildcard/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── assume_role_policy_action_wildcard.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── assume_role_policy_action_wildcard.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── assume_role_policy_notaction/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── assume_role_policy_notaction.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── assume_role_policy_notaction.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── assume_role_policy_notprincipal/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── assume_role_policy_notprincipal.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── assume_role_policy_notprincipal.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── assume_role_policy_version/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── assume_role_policy_version.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── assume_role_policy_version.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_role_policy/
│ │ │ │ ├── role_policy_action_wildcard/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_action_wildcard.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── role_policy_notaction/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notaction.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── role_policy_notresource/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── policy_notresource.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── role_policy_resource_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_resource_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── iam_user_policy/
│ │ │ │ └── exists/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── resource_exists.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── resource_exists.tf
│ │ │ │ └── test.yml
│ │ │ └── iam_user_policy_attachment/
│ │ │ └── exists/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── resource_exists.tf
│ │ │ ├── terraform12/
│ │ │ │ └── resource_exists.tf
│ │ │ └── test.yml
│ │ ├── iot/
│ │ │ └── iot_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── kinesis/
│ │ │ └── kinesis_stream/
│ │ │ ├── kinesis_stream_encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encryption.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption.tf
│ │ │ │ └── test.yml
│ │ │ └── kinesis_stream_kms_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key.tf
│ │ │ └── test.yml
│ │ ├── kinesis_firehouse/
│ │ │ └── kinesis_firehose_delivery_stream/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── encryption.tf
│ │ │ ├── terraform12/
│ │ │ │ └── encryption.tf
│ │ │ └── test.yml
│ │ ├── kms/
│ │ │ └── kms_key/
│ │ │ ├── rotation/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── rotation.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── rotation.tf
│ │ │ │ └── test.yml
│ │ │ └── wildcard_policy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_policy.tf
│ │ │ └── test.yml
│ │ ├── lambda/
│ │ │ ├── lambda_function/
│ │ │ │ ├── encryption/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── encryption.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encryption.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── environment_variables_aws_secrets/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── environment_variables_aws_secrets.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── environment_variables_aws_secrets.tf
│ │ │ │ └── test.yml
│ │ │ └── lambda_permission/
│ │ │ ├── action/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── action.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── action.tf
│ │ │ │ └── test.yml
│ │ │ └── principal_wildcard/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── principal_wildcard.tf
│ │ │ ├── terraform12/
│ │ │ │ └── principal_wildcard.tf
│ │ │ └── test.yml
│ │ ├── mediastore/
│ │ │ └── media_store_container_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── neptune/
│ │ │ └── neptune_cluster/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── encryption.tf
│ │ │ └── test.yml
│ │ ├── opsworks/
│ │ │ └── opsworks_application/
│ │ │ └── require_ssl/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── require_ssl.tf
│ │ │ └── test.yml
│ │ ├── rds/
│ │ │ ├── db_instance/
│ │ │ │ ├── encryption/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── storage_encryption.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── storage_encryption.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── publicly_accessible/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ └── test.yml
│ │ │ └── rds_cluster/
│ │ │ └── encryption/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── storage_encryption.tf
│ │ │ └── test.yml
│ │ ├── redshift/
│ │ │ ├── redshift_cluster/
│ │ │ │ ├── encrypted/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── encrypted.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── enhanced_vpc_routing/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── enhanced_vpc_routing.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── enhanced_vpc_routing.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── kms_key_id/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── kms_key_id.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── kms_key_id.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── logging/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── logging.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── logging.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── publicly_accessible/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── publicly_accessible.tf
│ │ │ │ └── test.yml
│ │ │ └── redshift_parameter_group/
│ │ │ ├── require_ssl/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── require_ssl.tf
│ │ │ │ └── test.yml
│ │ │ └── user_logging/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── user_logging.tf
│ │ │ └── test.yml
│ │ ├── s3/
│ │ │ ├── s3_bucket/
│ │ │ │ ├── acl_not_public/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── acl_not_public.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── acl_not_public.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── server_side_encryption_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── server_side_encryption_enabled.tf
│ │ │ │ └── test.yml
│ │ │ ├── s3_bucket_object/
│ │ │ │ └── encryption_enabled/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encryption_enabled.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption_enabled.tf
│ │ │ │ └── test.yml
│ │ │ └── s3_bucket_policy/
│ │ │ ├── policy_statement_action_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notaction/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notprincipal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_principal_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ └── policy_statement_secure_transport/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── policy_statement_secure_transport.tf
│ │ │ ├── terraform12/
│ │ │ │ └── policy_statement_secure_transport.tf
│ │ │ └── test.yml
│ │ ├── sagemaker/
│ │ │ ├── sagemaker_endpoint_configuration/
│ │ │ │ └── kms_key/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── kms_key.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── kms_key.tf
│ │ │ │ └── test.yml
│ │ │ └── sagemaker_notebook_instance/
│ │ │ └── kms_key/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── kms_key.tf
│ │ │ ├── terraform12/
│ │ │ │ └── kms_key.tf
│ │ │ └── test.yml
│ │ ├── ses/
│ │ │ └── ses_identity_policy/
│ │ │ └── wildcard_principal/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform12/
│ │ │ │ └── wildcard_principal.tf
│ │ │ └── test.yml
│ │ ├── shared/
│ │ │ ├── exists.todo.txt
│ │ │ ├── https.todo.txt
│ │ │ ├── kms_key.todo.txt
│ │ │ ├── policy.todo.txt
│ │ │ ├── policy_version.todo.txt
│ │ │ └── require_ssl.todo.txt
│ │ ├── sns/
│ │ │ ├── shared/
│ │ │ │ └── wildcard_principal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ ├── sns_topic_policy_wildcard_principal.tf
│ │ │ │ │ └── sns_topic_wildcard_principal.tf
│ │ │ │ └── test.yml
│ │ │ └── sns_topic_policy/
│ │ │ ├── topic_policy_statement_notaction/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ └── test.yml
│ │ │ ├── topic_policy_statement_notprincipal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ └── test.yml
│ │ │ └── topic_policy_statement_principal_wildcard-copy/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── policy_statement_principal_wildcard-copy.tf
│ │ │ ├── terraform12/
│ │ │ │ └── policy_statement_principal_wildcard-copy.tf
│ │ │ └── test.yml
│ │ ├── sqs/
│ │ │ ├── shared/
│ │ │ │ └── wildcard_principal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform12/
│ │ │ │ │ ├── sqs_queue_policy_wildcard_principal.tf
│ │ │ │ │ └── sqs_queue_wildcard_principal.tf
│ │ │ │ └── test.yml
│ │ │ ├── sqs_queue/
│ │ │ │ └── encryption/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── encryption.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── encryption.tf
│ │ │ │ └── test.yml
│ │ │ └── sqs_queue_policy/
│ │ │ ├── policy_statement_action_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_action_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notaction/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notaction.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_notprincipal/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_notprincipal.tf
│ │ │ │ └── test.yml
│ │ │ ├── policy_statement_principal_wildcard/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── policy_statement_principal_wildcard.tf
│ │ │ │ └── test.yml
│ │ │ └── policy_version/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── policy_version.tf
│ │ │ ├── terraform12/
│ │ │ │ └── policy_version.tf
│ │ │ └── test.yml
│ │ ├── vpc/
│ │ │ ├── security_group/
│ │ │ │ ├── egress_all_protocols/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── egress_all_protocols.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── egress_all_protocols.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── egress_port_range/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── egress_port_range.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── egress_port_range.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── ingress_all_protocols/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── ingress_all_protocols.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── ingress_all_protocols.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── ingress_port_range/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── ingress_port_range.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── ingress_port_range.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── missing_egress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── missing_egress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── missing_egress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── non_32_ingress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── non_32_ingress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── non_32_ingress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── rdp_world_ingress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── rdp_world_ingress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── rdp_world_ingress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── ssh_world_ingress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── ssh_world_ingress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── ssh_world_ingress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ ├── world_egress/
│ │ │ │ │ ├── rule.yml
│ │ │ │ │ └── tests/
│ │ │ │ │ ├── terraform11/
│ │ │ │ │ │ └── world_egress.tf
│ │ │ │ │ ├── terraform12/
│ │ │ │ │ │ └── world_egress.tf
│ │ │ │ │ └── test.yml
│ │ │ │ └── world_ingress/
│ │ │ │ ├── rule.yml
│ │ │ │ └── tests/
│ │ │ │ ├── terraform11/
│ │ │ │ │ └── world_ingress.tf
│ │ │ │ ├── terraform12/
│ │ │ │ │ └── world_ingress.tf
│ │ │ │ └── test.yml
│ │ │ └── subnet/
│ │ │ └── map_public_ip_on_launch/
│ │ │ ├── rule.yml
│ │ │ └── tests/
│ │ │ ├── terraform11/
│ │ │ │ └── map_public_ip_on_launch.tf
│ │ │ ├── terraform12/
│ │ │ │ └── map_public_ip_on_launch.tf
│ │ │ └── test.yml
│ │ └── waf/
│ │ └── waf_web_acl/
│ │ └── default_action_type/
│ │ ├── rule.yml
│ │ └── tests/
│ │ ├── terraform11/
│ │ │ └── default_action_type.tf
│ │ ├── terraform12/
│ │ │ └── default_action_type.tf
│ │ └── test.yml
│ ├── builtin_test.go
│ ├── options.go
│ ├── options_test.go
│ ├── report_writer.go
│ ├── report_writer_test.go
│ ├── terraform_test.go
│ └── testdata/
│ ├── builtin/
│ │ └── terraform12/
│ │ └── test.tf
│ ├── dirtest/
│ │ ├── a.yml
│ │ └── b.yml
│ ├── exclude-list
│ ├── profile-exceptions.yml
│ ├── profile.yml
│ ├── smoketest_exceptions.tf
│ ├── smoketest_tf11.tf
│ ├── smoketest_tf12.tf
│ ├── syntax-errors.yml
│ └── terraform.yml
├── docs/
│ ├── README.md
│ ├── conditions.md
│ ├── coverpage.md
│ ├── css/
│ │ └── style.css
│ ├── design.md
│ ├── development.md
│ ├── example-rules.md
│ ├── faq.md
│ ├── github_workflow.md
│ ├── index.html
│ ├── install.md
│ ├── operations.md
│ ├── output.md
│ ├── profiles.md
│ ├── rule_development.md
│ ├── rules.md
│ ├── running.md
│ ├── sidebar.md
│ ├── terraform.md
│ ├── tests.md
│ ├── value_from.md
│ └── yaml.md
├── example-files/
│ ├── config/
│ │ ├── cloudfront.tf
│ │ ├── elb.tf
│ │ ├── generic.config
│ │ ├── iam.tf
│ │ ├── my-pod.yml
│ │ ├── network-policy-1.yml
│ │ ├── network-policy-2.yml
│ │ ├── no-containers.yml
│ │ ├── not-a-pod.yml
│ │ ├── pod-nginx.yml
│ │ ├── pod-redis.yml
│ │ ├── policy.yml
│ │ ├── provider.tf
│ │ ├── s3-bucket-policy.tf
│ │ ├── s3-encryption.tf
│ │ ├── s3.tf
│ │ ├── search-debug.tf
│ │ ├── security_group.tf
│ │ ├── service-account.yml
│ │ ├── sns.tf
│ │ ├── sqs.tf
│ │ ├── terraform.tf
│ │ ├── variables.tf
│ │ ├── volumes.tf
│ │ └── web-and-helper.yml
│ ├── demo-resources/
│ │ └── s3-bucket.tf
│ └── rules/
│ ├── alias.yml
│ ├── generic-json.yml
│ ├── generic-yaml.yml
│ ├── iam-policies.yml
│ ├── iam-restricted.yml
│ ├── kubernetes.yml
│ ├── lint-rules-with-error.yml
│ ├── no-iam-actions.yml
│ ├── s3-encryption.yml
│ ├── terraform-more.yml
│ ├── terraform.yml
│ └── variables.tf
├── go.mod
├── go.sum
└── linter/
├── common.go
├── common_test.go
├── csv_resource_loader.go
├── csv_resource_loader_test.go
├── file_linter.go
├── file_linter_test.go
├── helpers_test.go
├── json_resource_loader.go
├── json_resource_loader_test.go
├── kubernetes.go
├── kubernetes_test.go
├── linter.go
├── linter_test.go
├── resource_linter.go
├── resource_linter_test.go
├── rules_resource_loader.go
├── rules_resource_loader_test.go
├── schema.go
├── terraform.go
├── terraform_interpolate.go
├── terraform_interpolate_test.go
├── terraform_test.go
├── terraform_v12.go
├── terraform_v12_test.go
├── testdata/
│ ├── data/
│ │ ├── bucket_name
│ │ ├── multi_line_content
│ │ ├── reference_relative.tf
│ │ ├── template_file_example_basic
│ │ ├── template_file_example_conditional
│ │ └── template_file_example_for_loop
│ ├── resources/
│ │ ├── batch_privileged.tf
│ │ ├── cloudfront_access_logs.tf
│ │ ├── defines_variables.tf
│ │ ├── dms_endpoint_encryption.tf
│ │ ├── dynamic_block.tf
│ │ ├── ec2_public.tf
│ │ ├── elasticache_encryption_rest.tf
│ │ ├── elasticache_encryption_transit.tf
│ │ ├── embedded_yaml.yml
│ │ ├── empty_document.yml
│ │ ├── emr_cluster_logs.tf
│ │ ├── explicit_chars.tf
│ │ ├── generic.config
│ │ ├── invalid.yml
│ │ ├── kinesis_kms_stream.tf
│ │ ├── kms_key_rotation.tf
│ │ ├── missing_kind.yml
│ │ ├── multi_level.tf
│ │ ├── multiple_blocks_same.tf
│ │ ├── multiple_pods.yml
│ │ ├── neptune_db_encryption.tf
│ │ ├── nullable_value.tf
│ │ ├── pod.yml
│ │ ├── policy_with_expression.tf
│ │ ├── policy_with_variables.tf
│ │ ├── rds_publicly_available.tf
│ │ ├── reference_file.tf
│ │ ├── reference_file_multi_line.tf
│ │ ├── reference_variables.tf
│ │ ├── sagemaker_endpoint_encryption.tf
│ │ ├── sagemaker_notebook_encryption.tf
│ │ ├── tagging.tf
│ │ ├── template_file_function_basic.tf
│ │ ├── template_file_function_conditional.tf
│ │ ├── template_file_function_for_loop.tf
│ │ ├── terraform_data.tf
│ │ ├── terraform_inner_objects.tf
│ │ ├── terraform_instance.tf
│ │ ├── terraform_module.tf
│ │ ├── terraform_policy.tf
│ │ ├── terraform_policy_empty.tf
│ │ ├── terraform_policy_invalid_json.tf
│ │ ├── terraform_provider.tf
│ │ ├── terraform_syntax_error.tf
│ │ ├── tf12_for_loop.tf
│ │ ├── tf12_resource_dependency.tf
│ │ ├── users.csv
│ │ ├── users.json
│ │ ├── uses_local_variables.tf
│ │ ├── uses_tf12_variables.tf
│ │ └── uses_variables.tf
│ └── rules/
│ ├── aggregate.yml
│ ├── bad-format.yml
│ ├── batch_definition.yml
│ ├── cloudfront_access_logs.yml
│ ├── dms_endpoint_encryption.yml
│ ├── dynamic_block.yml
│ ├── ec2_public.yml
│ ├── elasticache_encryption_rest.yml
│ ├── elasticache_encryption_transit.yml
│ ├── emr_cluster_logs.yml
│ ├── exclude_resource.yml
│ ├── explicit_chars.yml
│ ├── generic-csv.yml
│ ├── generic-json.yml
│ ├── generic-yaml.yml
│ ├── kinesis_kms_stream.yml
│ ├── kms_key_rotation.yml
│ ├── kubernetes.yml
│ ├── neptune_db_encryption.yml
│ ├── nullable_value.yml
│ ├── policy_variable.yml
│ ├── rds_publicly_available.yml
│ ├── rules.yml
│ ├── sagemaker_endpoint_encryption.yml
│ ├── sagemaker_notebook_encryption.yml
│ ├── tagging.yml
│ ├── terraform_bucket.yml
│ ├── terraform_data.yml
│ ├── terraform_instance.yml
│ ├── terraform_module.yml
│ ├── terraform_policy.yml
│ ├── terraform_provider.yml
│ ├── terraform_v12_variables.yml
│ ├── tf12_for_loop.yml
│ └── unknown.yml
├── tf12parser/
│ ├── README.md
│ ├── attribute.go
│ ├── block.go
│ ├── parser.go
│ ├── parser_test.go
│ ├── range.go
│ └── schema.go
├── yaml_resource_loader.go
└── yaml_resource_loader_test.go
SYMBOL INDEX (466 symbols across 61 files)
FILE: assertion/compare.go
function intCompare (line 8) | func intCompare(n1 int, n2 int) int {
function daysOld (line 18) | func daysOld(data interface{}) int {
function compare (line 32) | func compare(data interface{}, value string, valueType string) int {
FILE: assertion/compare_test.go
function TestDaysOldForToday (line 9) | func TestDaysOldForToday(t *testing.T) {
function TestDaysOldFor90DaysAgo (line 14) | func TestDaysOldFor90DaysAgo(t *testing.T) {
FILE: assertion/contains.go
function interfaceListContains (line 7) | func interfaceListContains(v []interface{}, key, value string) (MatchRes...
function stringListContains (line 21) | func stringListContains(v []string, key, value string) (MatchResult, err...
function stringContains (line 33) | func stringContains(v string, key, value string) (MatchResult, error) {
function defaultContains (line 40) | func defaultContains(data interface{}, key, value string) (MatchResult, ...
function contains (line 51) | func contains(data interface{}, key, value string) (MatchResult, error) {
function doesNotContain (line 64) | func doesNotContain(data interface{}, key, value string) (MatchResult, e...
function startsWith (line 75) | func startsWith(data interface{}, key, prefix string) (MatchResult, erro...
function endsWith (line 87) | func endsWith(data interface{}, key, suffix string) (MatchResult, error) {
FILE: assertion/contains_test.go
function TestContainsWithNonJSONType (line 10) | func TestContainsWithNonJSONType(t *testing.T) {
function TestDoesNotContainWithNonJSONType (line 18) | func TestDoesNotContainWithNonJSONType(t *testing.T) {
function TestContainsWithString (line 26) | func TestContainsWithString(t *testing.T) {
function TestContainsWithSliceOfStrings (line 33) | func TestContainsWithSliceOfStrings(t *testing.T) {
FILE: assertion/expression.go
function searchAndMatch (line 3) | func searchAndMatch(expression Expression, resource Resource) (MatchResu...
function orExpression (line 16) | func orExpression(expressions []Expression, resource Resource) (MatchRes...
function xorExpression (line 29) | func xorExpression(expressions []Expression, resource Resource) (MatchRe...
function andExpression (line 46) | func andExpression(expressions []Expression, resource Resource) (MatchRe...
function notExpression (line 59) | func notExpression(expressions []Expression, resource Resource) (MatchRe...
function collectResources (line 73) | func collectResources(key string, resource Resource) ([]Resource, error) {
function everyExpression (line 93) | func everyExpression(collectionExpression CollectionExpression, resource...
function someExpression (line 112) | func someExpression(collectionExpression CollectionExpression, resource ...
function noneExpression (line 131) | func noneExpression(collectionExpression CollectionExpression, resource ...
function exactlyOneExpression (line 150) | func exactlyOneExpression(collectionExpression CollectionExpression, res...
function booleanExpression (line 171) | func booleanExpression(expression Expression, resource Resource) (MatchR...
function ExcludeResource (line 200) | func ExcludeResource(rule Rule, resource Resource) bool {
function FilterResourceExceptions (line 210) | func FilterResourceExceptions(rule Rule, resources []Resource) []Resource {
function CheckExpression (line 224) | func CheckExpression(rule Rule, expression Expression, resource Resource...
FILE: assertion/expression_test.go
type ExpressionTestCase (line 8) | type ExpressionTestCase struct
function TestCheckExpression (line 14) | func TestCheckExpression(t *testing.T) {
function TestNestedBooleans (line 406) | func TestNestedBooleans(t *testing.T) {
function TestExceptions (line 469) | func TestExceptions(t *testing.T) {
function TestNoExceptions (line 486) | func TestNoExceptions(t *testing.T) {
function TestUsingFixtures (line 503) | func TestUsingFixtures(t *testing.T) {
FILE: assertion/has_properties.go
function hasProperties (line 7) | func hasProperties(data interface{}, list string) (MatchResult, error) {
FILE: assertion/helper_test.go
type FixtureTestCases (line 11) | type FixtureTestCases struct
type FixtureTestCase (line 17) | type FixtureTestCase struct
function FailTestIfError (line 26) | func FailTestIfError(err error, message string, t *testing.T) {
function LoadTestCasesFromFixture (line 33) | func LoadTestCasesFromFixture(filename string, t *testing.T) FixtureTest...
function RunTestCasesFromFixture (line 49) | func RunTestCasesFromFixture(filename string, t *testing.T) {
FILE: assertion/invoke.go
type InvokeViolation (line 12) | type InvokeViolation struct
type InvokeResponse (line 17) | type InvokeResponse struct
type StandardExternalRuleInvoker (line 22) | type StandardExternalRuleInvoker struct
method Invoke (line 45) | func (e StandardExternalRuleInvoker) Invoke(rule Rule, resource Resour...
function makeViolation (line 25) | func makeViolation(rule Rule, resource Resource, message string) Violati...
function makeViolations (line 39) | func makeViolations(rule Rule, resource Resource, message string) []Viol...
FILE: assertion/invoke_test.go
function TestInvokeOK (line 13) | func TestInvokeOK(t *testing.T) {
function TestInvokeWithViolations (line 33) | func TestInvokeWithViolations(t *testing.T) {
function TestInvokeSendsMetadata (line 60) | func TestInvokeSendsMetadata(t *testing.T) {
FILE: assertion/ip_operations.go
function getIPObject (line 13) | func getIPObject(addressString string) (net.IP, error) {
function isSubnet (line 24) | func isSubnet(ipAddressStr string, supernet string) bool {
function isPrivateIP (line 37) | func isPrivateIP(ipAddressStr string) bool {
function maxHostCount (line 46) | func maxHostCount(ruleCidr string, hostLimitStr string) bool {
function hostCountByNetmaskOnes (line 64) | func hostCountByNetmaskOnes(netmaskOnes int) int {
FILE: assertion/ip_operations_test.go
function TestIsSubnet (line 19) | func TestIsSubnet(t *testing.T) {
function TestIsPrivateIp (line 48) | func TestIsPrivateIp(t *testing.T) {
function TestMaxHostCount (line 73) | func TestMaxHostCount(t *testing.T) {
FILE: assertion/log.go
function SetDebug (line 10) | func SetDebug(b bool) {
function Debugf (line 15) | func Debugf(format string, args ...interface{}) {
function DebugJSON (line 22) | func DebugJSON(title string, object interface{}) {
FILE: assertion/match.go
function matches (line 9) | func matches() (MatchResult, error) {
function doesNotMatch (line 13) | func doesNotMatch(format string, args ...interface{}) (MatchResult, erro...
function matchError (line 20) | func matchError(err error) (MatchResult, error) {
function isMatch (line 27) | func isMatch(data interface{}, expression Expression) (MatchResult, erro...
FILE: assertion/match_test.go
type MatchTestCase (line 9) | type MatchTestCase struct
function getQuotesRight (line 17) | func getQuotesRight(jsonString string) string {
function unmarshal (line 27) | func unmarshal(s string) (interface{}, error) {
function TestIsMatch (line 39) | func TestIsMatch(t *testing.T) {
FILE: assertion/rules.go
function ParseRules (line 9) | func ParseRules(rules string) (RuleSet, error) {
function FilterRulesByTag (line 16) | func FilterRulesByTag(rules []Rule, tags []string) []Rule {
function FilterRulesByID (line 27) | func FilterRulesByID(rules []Rule, ruleIDs []string, ignoreRuleIDs []str...
function uniqueRules (line 54) | func uniqueRules(list []Rule) []Rule {
function FilterRulesByTagAndID (line 67) | func FilterRulesByTagAndID(rules []Rule, tags []string, ruleIds []string...
function ResolveRules (line 81) | func ResolveRules(rules []Rule, valueSource ValueSource) ([]Rule, []Viol...
function ResolveRule (line 93) | func ResolveRule(rule Rule, valueSource ValueSource) (Rule, []Violation) {
function CheckRule (line 121) | func CheckRule(rule Rule, resource Resource, e ExternalRuleInvoker) (str...
function JoinRuleSets (line 170) | func JoinRuleSets(firstSet RuleSet, secondSet RuleSet) (RuleSet, error) {
FILE: assertion/rules_test.go
type TestValueSource (line 10) | type TestValueSource struct
method GetValue (line 12) | func (t TestValueSource) GetValue(expression Expression) (string, erro...
function testValueSource (line 19) | func testValueSource() ValueSource {
type TestValueSourceWithError (line 25) | type TestValueSourceWithError struct
method GetValue (line 27) | func (t TestValueSourceWithError) GetValue(expression Expression) (str...
function testValueSourceWithError (line 31) | func testValueSourceWithError() ValueSource {
type MockExternalRuleInvoker (line 37) | type MockExternalRuleInvoker
method Invoke (line 44) | func (e *MockExternalRuleInvoker) Invoke(Rule, Resource) (string, []Vi...
function mockExternalRuleInvoker (line 39) | func mockExternalRuleInvoker() *MockExternalRuleInvoker {
function MustParseRules (line 84) | func MustParseRules(content string, t *testing.T) RuleSet {
function TestParseRules (line 92) | func TestParseRules(t *testing.T) {
type FilterTestCase (line 99) | type FilterTestCase struct
function TestFilterRules (line 106) | func TestFilterRules(t *testing.T) {
function TestFilterRulesByTagAndID (line 127) | func TestFilterRulesByTagAndID(t *testing.T) {
function TestRuleWithMultipleFilter (line 155) | func TestRuleWithMultipleFilter(t *testing.T) {
function TestMultipleFiltersWithSingleFailure (line 175) | func TestMultipleFiltersWithSingleFailure(t *testing.T) {
function TestMultipleFiltersWithMultipleFailures (line 195) | func TestMultipleFiltersWithMultipleFailures(t *testing.T) {
function TestValueFrom (line 228) | func TestValueFrom(t *testing.T) {
function TestResolveRuleError (line 252) | func TestResolveRuleError(t *testing.T) {
function TestInvokeRule (line 274) | func TestInvokeRule(t *testing.T) {
FILE: assertion/search.go
function SearchData (line 8) | func SearchData(expression string, data interface{}) (interface{}, error) {
FILE: assertion/types.go
type Resource (line 6) | type Resource struct
type RuleSet (line 16) | type RuleSet struct
type Rule (line 28) | type Rule struct
type Expression (line 44) | type Expression struct
type CollectionExpression (line 61) | type CollectionExpression struct
type ValueFrom (line 67) | type ValueFrom struct
type InvokeRuleAPI (line 73) | type InvokeRuleAPI struct
type ResourceConfig (line 79) | type ResourceConfig struct
type ColumnConfig (line 86) | type ColumnConfig struct
type ValidationReport (line 91) | type ValidationReport struct
type Violation (line 98) | type Violation struct
type ScannedResource (line 112) | type ScannedResource struct
type ValueSource (line 122) | type ValueSource interface
type ExternalRuleInvoker (line 127) | type ExternalRuleInvoker interface
type MatchResult (line 132) | type MatchResult struct
type Result (line 138) | type Result struct
FILE: assertion/util.go
function unquoted (line 10) | func unquoted(s string) string {
function quoted (line 17) | func quoted(s string) string {
function isAbsent (line 21) | func isAbsent(s string) bool {
function isPresent (line 28) | func isPresent(s string) bool {
function isEmpty (line 32) | func isEmpty(data interface{}) bool {
function isArray (line 48) | func isArray(data interface{}) bool {
function listsIntersect (line 63) | func listsIntersect(list1 []string, list2 []string) bool {
function jsonListsIntersect (line 74) | func jsonListsIntersect(s1 string, s2 string) bool {
function ShouldIncludeFile (line 89) | func ShouldIncludeFile(patterns []string, filename string) (bool, error) {
function FilterResourcesByType (line 107) | func FilterResourcesByType(resources []Resource, resourceType string, re...
function FilterResourcesByTypes (line 121) | func FilterResourcesByTypes(resources []Resource, resourceTypes []string...
function categoryMatches (line 131) | func categoryMatches(c1, c2 string) bool {
function JSONStringify (line 139) | func JSONStringify(data interface{}) (string, error) {
function currentTime (line 147) | func currentTime() string {
function SliceContains (line 151) | func SliceContains(list []string, value string) bool {
function ExcludeResourceTypes (line 161) | func ExcludeResourceTypes(resources []Resource, resourceTypes []string, ...
function FilterResourcesForRule (line 172) | func FilterResourcesForRule(resources []Resource, rule Rule) []Resource {
FILE: assertion/util_test.go
function TestUnquotedWithoutQuotes (line 8) | func TestUnquotedWithoutQuotes(t *testing.T) {
function TestUnquotedWithQuotes (line 14) | func TestUnquotedWithQuotes(t *testing.T) {
function TestIsAbsentEmptyString (line 20) | func TestIsAbsentEmptyString(t *testing.T) {
function TestIsAbsentEmptyArray (line 26) | func TestIsAbsentEmptyArray(t *testing.T) {
function TestIsAbsentNull (line 32) | func TestIsAbsentNull(t *testing.T) {
function TestIsAbsentFalse (line 38) | func TestIsAbsentFalse(t *testing.T) {
function TestIntersectTrue (line 44) | func TestIntersectTrue(t *testing.T) {
function TestIntersectFalse (line 52) | func TestIntersectFalse(t *testing.T) {
function TestJSONListsIntersectTrue (line 60) | func TestJSONListsIntersectTrue(t *testing.T) {
function TestShouldIncludeFile (line 68) | func TestShouldIncludeFile(t *testing.T) {
function TestShouldNotIncludeFile (line 79) | func TestShouldNotIncludeFile(t *testing.T) {
function TestFilterShouldIncludeResources (line 90) | func TestFilterShouldIncludeResources(t *testing.T) {
function TestFilterShouldExcludeResources (line 101) | func TestFilterShouldExcludeResources(t *testing.T) {
function TestFilterShouldIncludeAllResources (line 112) | func TestFilterShouldIncludeAllResources(t *testing.T) {
function TestFilterShouldMatchCategoryForResources (line 123) | func TestFilterShouldMatchCategoryForResources(t *testing.T) {
function TestSliceContainsTrue (line 134) | func TestSliceContainsTrue(t *testing.T) {
function TestSliceContainsFalse (line 142) | func TestSliceContainsFalse(t *testing.T) {
function TestFilterPluralShouldMatchMultipleResources (line 150) | func TestFilterPluralShouldMatchMultipleResources(t *testing.T) {
function TestFilterPluralShouldNotHaveUnlistedResources (line 161) | func TestFilterPluralShouldNotHaveUnlistedResources(t *testing.T) {
function TestFilterResourcesForRuleSlice (line 173) | func TestFilterResourcesForRuleSlice(t *testing.T) {
function TestFilterResourcesForRuleString (line 190) | func TestFilterResourcesForRuleString(t *testing.T) {
function TestFilterResourcesForWildcard (line 204) | func TestFilterResourcesForWildcard(t *testing.T) {
function TestFilterResourcesForDefault (line 218) | func TestFilterResourcesForDefault(t *testing.T) {
function TestFilterExcludeResourcesForRuleString (line 230) | func TestFilterExcludeResourcesForRuleString(t *testing.T) {
FILE: assertion/value.go
type StandardValueSource (line 17) | type StandardValueSource struct
method GetValue (line 22) | func (v StandardValueSource) GetValue(expression Expression) (string, ...
method GetValueFromS3 (line 52) | func (v StandardValueSource) GetValueFromS3(bucket string, key string)...
method GetValueFromHTTP (line 94) | func (v StandardValueSource) GetValueFromHTTP(url string) (string, err...
function getBucketRegion (line 77) | func getBucketRegion(bucket string) (string, error) {
FILE: assertion/value_test.go
function TestCommandLineVariable (line 11) | func TestCommandLineVariable(t *testing.T) {
function TestValueFromHttp (line 27) | func TestValueFromHttp(t *testing.T) {
FILE: cli/app.go
type LinterOptions (line 25) | type LinterOptions struct
type ProfileOptions (line 37) | type ProfileOptions struct
type RuleException (line 52) | type RuleException struct
type CommandLineOptions (line 61) | type CommandLineOptions struct
type ReportWriter (line 82) | type ReportWriter interface
type DefaultReportWriter (line 87) | type DefaultReportWriter struct
function main (line 92) | func main() {
function addExceptions (line 160) | func addExceptions(ruleSets []assertion.RuleSet, exceptions []RuleExcept...
function addExceptionsToRuleSet (line 168) | func addExceptionsToRuleSet(ruleSet assertion.RuleSet, exceptions []Rule...
function resourceMatch (line 182) | func resourceMatch(rule assertion.Rule, exception RuleException) bool {
function categoryMatch (line 189) | func categoryMatch(rule assertion.Rule, exception RuleException) bool {
function validateRules (line 193) | func validateRules(filenames []string, w ReportWriter) (int, error) {
function loadRuleSets (line 205) | func loadRuleSets(args arrayFlags) ([]assertion.RuleSet, error) {
function isYamlFile (line 223) | func isYamlFile(filename string) bool {
function yamlFilesOnly (line 230) | func yamlFilesOnly(filenames []string) []string {
function loadBuiltInRuleSet (line 243) | func loadBuiltInRuleSet(filename string) (assertion.RuleSet, error) {
function addRuleSet (line 279) | func addRuleSet(ruleSet assertion.RuleSet, box packr.Box, filename strin...
function getRuleSet (line 299) | func getRuleSet(box packr.Box, name string) (assertion.RuleSet, error) {
function applyRules (line 313) | func applyRules(ruleSets []assertion.RuleSet, args arrayFlags, options L...
function printReport (line 352) | func printReport(w io.Writer, report assertion.ValidationReport, queryEx...
type arrayFlags (line 377) | type arrayFlags
method String (line 379) | func (i *arrayFlags) String() string {
method Set (line 386) | func (i *arrayFlags) Set(value string) error {
function generateExitCode (line 391) | func generateExitCode(report assertion.ValidationReport) int {
function loadFilenames (line 400) | func loadFilenames(commandLineFilenames []string, profileFilenames []str...
function defaultToCurrentDirectory (line 410) | func defaultToCurrentDirectory(filenames []string) []string {
function excludeFilenames (line 417) | func excludeFilenames(filenames []string, excludePatterns []string) []st...
function excludeFilename (line 428) | func excludeFilename(filename string, excludePatterns []string) bool {
function getFilenames (line 439) | func getFilenames(args []string) []string {
function getFilesInDirectory (line 462) | func getFilesInDirectory(root string) []string {
FILE: cli/app_test.go
function TestLoadTerraformRules (line 13) | func TestLoadTerraformRules(t *testing.T) {
function TestLoadValidateRules (line 20) | func TestLoadValidateRules(t *testing.T) {
function TestExcludeAll (line 27) | func TestExcludeAll(t *testing.T) {
function TestExcludeSubdirectory (line 36) | func TestExcludeSubdirectory(t *testing.T) {
function TestExcludeOnePattern (line 45) | func TestExcludeOnePattern(t *testing.T) {
function TestExcludeMultiplePattern (line 54) | func TestExcludeMultiplePattern(t *testing.T) {
function TestExcludeFrom (line 63) | func TestExcludeFrom(t *testing.T) {
function TestProfileExceptions (line 80) | func TestProfileExceptions(t *testing.T) {
function TestBuiltRules (line 107) | func TestBuiltRules(t *testing.T) {
function TestPrintReport (line 137) | func TestPrintReport(t *testing.T) {
function TestPrintReportWithQueryString (line 147) | func TestPrintReportWithQueryString(t *testing.T) {
type MockReportWriter (line 162) | type MockReportWriter struct
method WriteReport (line 166) | func (w MockReportWriter) WriteReport(r assertion.ValidationReport, o ...
function TestApplyRules (line 170) | func TestApplyRules(t *testing.T) {
function TestValidateRules (line 184) | func TestValidateRules(t *testing.T) {
function TestResourceMatch (line 191) | func TestResourceMatch(t *testing.T) {
function TestLoadRuleSetsBadFilename (line 233) | func TestLoadRuleSetsBadFilename(t *testing.T) {
function TestLoadRuleSetsParseErrors (line 239) | func TestLoadRuleSetsParseErrors(t *testing.T) {
function TestStdinFilename (line 248) | func TestStdinFilename(t *testing.T) {
function TestGetFilenamesUsingDirectory (line 254) | func TestGetFilenamesUsingDirectory(t *testing.T) {
function TestLoadFilenamesFromCommandLine (line 261) | func TestLoadFilenamesFromCommandLine(t *testing.T) {
function TestLoadFilenamesFromProfile (line 268) | func TestLoadFilenamesFromProfile(t *testing.T) {
function TestArrayFlags (line 275) | func TestArrayFlags(t *testing.T) {
function TestLoadBuiltInRuleSetMissing (line 283) | func TestLoadBuiltInRuleSetMissing(t *testing.T) {
FILE: cli/builtin_test.go
function loadRules (line 11) | func loadRules(t *testing.T, filename string) assertion.RuleSet {
type BuiltInTestCase (line 17) | type BuiltInTestCase struct
function numberOfWarnings (line 24) | func numberOfWarnings(violations []assertion.Violation) int {
function numberOfFailures (line 33) | func numberOfFailures(violations []assertion.Violation) int {
function getViolationsString (line 44) | func getViolationsString(severity string, violations []assertion.Violati...
FILE: cli/options.go
function getCommandLineOptions (line 14) | func getCommandLineOptions() CommandLineOptions {
function getLinterOptions (line 41) | func getLinterOptions(o CommandLineOptions, p ProfileOptions) (LinterOpt...
function loadProfile (line 65) | func loadProfile(filename string) (ProfileOptions, error) {
function makeTagList (line 96) | func makeTagList(tags string, profileOptions []string) []string {
function makeRulesList (line 106) | func makeRulesList(ruleIDs string, profileOptions []string) []string {
function makeQueryExpression (line 116) | func makeQueryExpression(queryExpression string, verboseReport bool, pro...
function parseVariables (line 131) | func parseVariables(vars []string) map[string]string {
function mergeVariables (line 144) | func mergeVariables(a, b map[string]string) map[string]string {
function loadExcludePatterns (line 157) | func loadExcludePatterns(patterns []string, excludeFromFilenames []strin...
function validateParser (line 176) | func validateParser(parser string) (string, error) {
FILE: cli/options_test.go
function emptyCommandLineOptions (line 7) | func emptyCommandLineOptions() CommandLineOptions {
function TestCommandLineOnlyOptions (line 21) | func TestCommandLineOnlyOptions(t *testing.T) {
function TestProfileOnlyOptions (line 36) | func TestProfileOnlyOptions(t *testing.T) {
function TestCommandLineOverridesProfile (line 51) | func TestCommandLineOverridesProfile(t *testing.T) {
function TestCommandLineVariables (line 68) | func TestCommandLineVariables(t *testing.T) {
function TestMergeVariables (line 87) | func TestMergeVariables(t *testing.T) {
function TestLoadProfile (line 116) | func TestLoadProfile(t *testing.T) {
function TestProfileExclude (line 126) | func TestProfileExclude(t *testing.T) {
function TestValidateParser (line 150) | func TestValidateParser(t *testing.T) {
FILE: cli/report_writer.go
method WriteReport (line 8) | func (w DefaultReportWriter) WriteReport(report assertion.ValidationRepo...
FILE: cli/report_writer_test.go
function TestReportWriter (line 10) | func TestReportWriter(t *testing.T) {
FILE: cli/terraform_test.go
type TestSuite (line 17) | type TestSuite struct
type TestCase (line 26) | type TestCase struct
function isTestCase (line 36) | func isTestCase(filename string) bool {
function loadTestSuite (line 47) | func loadTestSuite(filename string) (TestSuite, error) {
function getTestResources (line 65) | func getTestResources(directory string) ([]string, error) {
function contains (line 83) | func contains(arr []string, str string) bool {
function runTestSuite (line 93) | func runTestSuite(t *testing.T, ts TestSuite) {
function RunBuiltinTests (line 160) | func RunBuiltinTests(t *testing.T, resourceType string) {
function TestTerraformBuiltInRules (line 177) | func TestTerraformBuiltInRules(t *testing.T) {
FILE: linter/common.go
function readContent (line 15) | func readContent(filename string) ([]byte, error) {
function loadYAML (line 22) | func loadYAML(filename string) ([]interface{}, error) {
function splitYAMLDocs (line 48) | func splitYAMLDocs(content string) []string {
function docIsNotEmpty (line 68) | func docIsNotEmpty(s string) bool {
function loadJSON (line 72) | func loadJSON(filename string) ([]interface{}, error) {
function loadCSV (line 88) | func loadCSV(filename string) ([][]string, error) {
function getResourceIDFromFilename (line 96) | func getResourceIDFromFilename(filename string) string {
function CombineValidationReports (line 102) | func CombineValidationReports(r1, r2 assertion.ValidationReport) asserti...
FILE: linter/common_test.go
function TestLoadYamlFileError (line 10) | func TestLoadYamlFileError(t *testing.T) {
function TestLoadYamlParseError (line 17) | func TestLoadYamlParseError(t *testing.T) {
function TestLoadYamlUnexpectedFormat (line 27) | func TestLoadYamlUnexpectedFormat(t *testing.T) {
function TestLoadYamlMultipleDocuments (line 33) | func TestLoadYamlMultipleDocuments(t *testing.T) {
function TestLoadYamlWithEmbeddedYaml (line 39) | func TestLoadYamlWithEmbeddedYaml(t *testing.T) {
function TestLoadYamlWithEmptyDocument (line 45) | func TestLoadYamlWithEmptyDocument(t *testing.T) {
function TestGetResourceIDFromFilename (line 51) | func TestGetResourceIDFromFilename(t *testing.T) {
function TestCombineValidationReports (line 59) | func TestCombineValidationReports(t *testing.T) {
FILE: linter/csv_resource_loader.go
type CSVResourceLoader (line 9) | type CSVResourceLoader struct
method Load (line 23) | func (l CSVResourceLoader) Load(filename string) (FileResources, error) {
method PostLoad (line 50) | func (l CSVResourceLoader) PostLoad(r FileResources) ([]assertion.Reso...
function extractCSVResourceID (line 13) | func extractCSVResourceID(expression string, properties interface{}) str...
FILE: linter/csv_resource_loader_test.go
function TestCSVLinterValidate (line 9) | func TestCSVLinterValidate(t *testing.T) {
function TestCSVLinterSearch (line 24) | func TestCSVLinterSearch(t *testing.T) {
FILE: linter/file_linter.go
type Variable (line 13) | type Variable struct
type FileResources (line 18) | type FileResources struct
type FileResourceLoader (line 23) | type FileResourceLoader interface
type FileLinter (line 28) | type FileLinter struct
method Validate (line 36) | func (fl FileLinter) Validate(ruleSet assertion.RuleSet, options Optio...
method Search (line 119) | func (fl FileLinter) Search(ruleSet assertion.RuleSet, searchExpressio...
function filterFiles (line 75) | func filterFiles(fileNames []string, patterns []string) (ret []string) {
function iterateFiles (line 85) | func iterateFiles(fl FileLinter, ruleSet assertion.RuleSet, filesScanned...
function makeLoadViolation (line 104) | func makeLoadViolation(filename string, err error) assertion.Violation {
FILE: linter/file_linter_test.go
function TestFilterFiles (line 8) | func TestFilterFiles(t *testing.T) {
FILE: linter/helpers_test.go
function loadRulesForTest (line 9) | func loadRulesForTest(filename string, t *testing.T) assertion.RuleSet {
function assertViolationsCount (line 23) | func assertViolationsCount(testName string, count int, violations []asse...
function assertViolationByRuleID (line 30) | func assertViolationByRuleID(testName string, ruleID string, violations ...
FILE: linter/json_resource_loader.go
type JSONResourceLoader (line 9) | type JSONResourceLoader struct
method Load (line 23) | func (l JSONResourceLoader) Load(filename string) (FileResources, erro...
method PostLoad (line 58) | func (l JSONResourceLoader) PostLoad(r FileResources) ([]assertion.Res...
function extractJSONResourceID (line 13) | func extractJSONResourceID(expression string, properties interface{}) st...
FILE: linter/json_resource_loader_test.go
function TestJSONLinterValidate (line 9) | func TestJSONLinterValidate(t *testing.T) {
function TestJSONLinterSearch (line 25) | func TestJSONLinterSearch(t *testing.T) {
FILE: linter/kubernetes.go
type KubernetesLinter (line 10) | type KubernetesLinter struct
type KubernetesResourceLoader (line 16) | type KubernetesResourceLoader struct
method Load (line 28) | func (l KubernetesResourceLoader) Load(filename string) (FileResources...
method PostLoad (line 63) | func (l KubernetesResourceLoader) PostLoad(r FileResources) ([]asserti...
function getResourceIDFromMetadata (line 18) | func getResourceIDFromMetadata(m map[string]interface{}) (string, bool) {
FILE: linter/kubernetes_test.go
function TestKubernetesSpecialVariables (line 9) | func TestKubernetesSpecialVariables(t *testing.T) {
function TestKubernetesMissingKind (line 21) | func TestKubernetesMissingKind(t *testing.T) {
FILE: linter/linter.go
type Linter (line 12) | type Linter interface
type Options (line 18) | type Options struct
function NewLinter (line 26) | func NewLinter(ruleSet assertion.RuleSet, vs assertion.ValueSource, file...
FILE: linter/linter_test.go
type NewLinterTestCase (line 9) | type NewLinterTestCase struct
function TestNewLinter (line 14) | func TestNewLinter(t *testing.T) {
function TestUnknownLinterType (line 42) | func TestUnknownLinterType(t *testing.T) {
type MockValueSource (line 54) | type MockValueSource struct
method GetValue (line 56) | func (m MockValueSource) GetValue(e assertion.Expression) (string, err...
FILE: linter/resource_linter.go
type ResourceLinter (line 8) | type ResourceLinter struct
method ValidateResources (line 13) | func (r ResourceLinter) ValidateResources(resources []assertion.Resour...
FILE: linter/resource_linter_test.go
function TestIgnoreResource (line 7) | func TestIgnoreResource(t *testing.T) {
FILE: linter/rules_resource_loader.go
type RulesResourceLoader (line 9) | type RulesResourceLoader struct
method Load (line 21) | func (l RulesResourceLoader) Load(filename string) (FileResources, err...
method PostLoad (line 59) | func (l RulesResourceLoader) PostLoad(r FileResources) ([]assertion.Re...
function getAttr (line 11) | func getAttr(m map[string]interface{}, keys ...string) []interface{} {
FILE: linter/rules_resource_loader_test.go
function TestRulesLinterValidate (line 9) | func TestRulesLinterValidate(t *testing.T) {
function TestRulesLinterSearch (line 25) | func TestRulesLinterSearch(t *testing.T) {
FILE: linter/terraform.go
type TerraformResourceLoader (line 19) | type TerraformResourceLoader struct
method Load (line 159) | func (l TerraformResourceLoader) Load(filename string) (FileResources,...
method PostLoad (line 274) | func (l TerraformResourceLoader) PostLoad(fr FileResources) ([]asserti...
type TerraformLoadResult (line 22) | type TerraformLoadResult struct
function loadHCL (line 32) | func loadHCL(filename string) (TerraformLoadResult, error) {
function loadVariables (line 81) | func loadVariables(data interface{}) []Variable {
function loadLocalVariables (line 96) | func loadLocalVariables(data interface{}) []Variable {
function getVariableValue (line 112) | func getVariableValue(key string, resource interface{}) interface{} {
function getVariableFromEnvironment (line 120) | func getVariableFromEnvironment(key string) interface{} {
function getVariableDefault (line 124) | func getVariableDefault(resource interface{}) interface{} {
function flattenMaps (line 136) | func flattenMaps(v interface{}) interface{} {
function getResourceLineNumber (line 148) | func getResourceLineNumber(resourceType, resourceID, filename string, ro...
function addIDToProviders (line 180) | func addIDToProviders(providers []interface{}) []interface{} {
function addIDToProvider (line 188) | func addIDToProvider(provider interface{}) interface{} {
function addIDToProviderValue (line 199) | func addIDToProviderValue(value interface{}) interface{} {
function addKeyToModules (line 208) | func addKeyToModules(modules []interface{}) []interface{} {
function addKeyToModule (line 216) | func addKeyToModule(resources map[string]interface{}, module interface{}...
function getResources (line 241) | func getResources(filename string, ast *ast.File, objects []interface{},...
function replaceVariables (line 288) | func replaceVariables(templateResource interface{}, variables []Variable...
function replaceVariablesInMap (line 300) | func replaceVariablesInMap(templateResource map[string]interface{}, vari...
function replaceVariablesInList (line 316) | func replaceVariablesInList(list []interface{}, variables []Variable) []...
function parseJSONDocuments (line 324) | func parseJSONDocuments(resource interface{}) (interface{}, error) {
function getProperties (line 344) | func getProperties(templateResource interface{}) map[string]interface{} {
FILE: linter/terraform_interpolate.go
function makeList (line 14) | func makeList(variables []interface{}) []ast.Variable {
function makeMap (line 22) | func makeMap(m map[string]interface{}) map[string]ast.Variable {
function makeVar (line 32) | func makeVar(v interface{}) ast.Variable {
function makeVarMap (line 58) | func makeVarMap(variables []Variable) map[string]ast.Variable {
function interpolationFuncFile (line 66) | func interpolationFuncFile() ast.Function {
function interpolationFuncLookup (line 86) | func interpolationFuncLookup() ast.Function {
function interpolationFuncJoin (line 128) | func interpolationFuncJoin() ast.Function {
function interpolationFuncConcat (line 159) | func interpolationFuncConcat() ast.Function {
function interpolationFuncFormat (line 200) | func interpolationFuncFormat() ast.Function {
function interpolationFuncList (line 215) | func interpolationFuncList() ast.Function {
function interpolationFuncReplace (line 254) | func interpolationFuncReplace() ast.Function {
function interpolationFuncElement (line 282) | func interpolationFuncElement() ast.Function {
function interpolationFuncMap (line 313) | func interpolationFuncMap() ast.Function {
function interpolationFuncMerge (line 355) | func interpolationFuncMerge() ast.Function {
function Funcs (line 375) | func Funcs() map[string]ast.Function {
function interpolate (line 390) | func interpolate(s string, variables []Variable) interface{} {
FILE: linter/terraform_interpolate_test.go
type interpolationTestCase (line 8) | type interpolationTestCase struct
function TestInterpolation (line 13) | func TestInterpolation(t *testing.T) {
FILE: linter/terraform_test.go
function TestTerraformLinter (line 11) | func TestTerraformLinter(t *testing.T) {
function loadResourcesToTest (line 26) | func loadResourcesToTest(t *testing.T, filename string) []assertion.Reso...
function getResourceTags (line 35) | func getResourceTags(r assertion.Resource) map[string]interface{} {
function TestTerraformVariable (line 41) | func TestTerraformVariable(t *testing.T) {
function TestTerraformVariableWithNoDefault (line 48) | func TestTerraformVariableWithNoDefault(t *testing.T) {
function TestTerraformFunctionCall (line 55) | func TestTerraformFunctionCall(t *testing.T) {
function TestTerraformListVariable (line 62) | func TestTerraformListVariable(t *testing.T) {
function TestTerraformLocalVariable (line 69) | func TestTerraformLocalVariable(t *testing.T) {
function TestTerraformVariablesFromEnvironment (line 76) | func TestTerraformVariablesFromEnvironment(t *testing.T) {
function TestTerraformFileFunction (line 85) | func TestTerraformFileFunction(t *testing.T) {
function TestTerraformVariablesInDifferentFile (line 92) | func TestTerraformVariablesInDifferentFile(t *testing.T) {
type TestingValueSource (line 110) | type TestingValueSource struct
method GetValue (line 112) | func (s TestingValueSource) GetValue(a assertion.Expression) (string, ...
function TestTerraformDataLoader (line 119) | func TestTerraformDataLoader(t *testing.T) {
type terraformLinterTestCase (line 126) | type terraformLinterTestCase struct
function TestTerraformLinterCases (line 133) | func TestTerraformLinterCases(t *testing.T) {
FILE: linter/terraform_v12.go
type Terraform12ResourceLoader (line 15) | type Terraform12ResourceLoader struct
method Load (line 50) | func (l Terraform12ResourceLoader) Load(filename string) (FileResource...
method LoadMany (line 64) | func (l Terraform12ResourceLoader) LoadMany(filenames []string) (FileR...
method PostLoad (line 266) | func (l Terraform12ResourceLoader) PostLoad(inputResources FileResourc...
type Terraform12LoadResult (line 18) | type Terraform12LoadResult struct
function loadHCLv2 (line 78) | func loadHCLv2(paths []string) (Terraform12LoadResult, error) {
function getBlocksOfType (line 112) | func getBlocksOfType(blocks tf12parser.Blocks, blockCategory string) []a...
function attributesToMap (line 170) | func attributesToMap(block tf12parser.Block) map[string]interface{} {
function iterateElements (line 209) | func iterateElements(propertyMap map[string]interface{}, name string, va...
function setValue (line 226) | func setValue(m map[string]interface{}, name string, value string) {
function ctyValueToString (line 235) | func ctyValueToString(value cty.Value) string {
FILE: linter/terraform_v12_test.go
function TestTerraformV12Linter (line 16) | func TestTerraformV12Linter(t *testing.T) {
function loadResources12ToTest (line 31) | func loadResources12ToTest(t *testing.T, filename string) []assertion.Re...
function filterByCategory (line 40) | func filterByCategory(t *testing.T, resources []assertion.Resource, cate...
function TestSingleResourceType (line 50) | func TestSingleResourceType(t *testing.T) {
function TestResourceDependency (line 58) | func TestResourceDependency(t *testing.T) {
function TestTupleType (line 65) | func TestTupleType(t *testing.T) {
function TestMultipleBlocksOfSameType12 (line 77) | func TestMultipleBlocksOfSameType12(t *testing.T) {
function TestInnerObjects12 (line 85) | func TestInnerObjects12(t *testing.T) {
function TestTerraform12Variable (line 94) | func TestTerraform12Variable(t *testing.T) {
function TestTerraform12VariableWithNoDefault (line 102) | func TestTerraform12VariableWithNoDefault(t *testing.T) {
function TestTerraform12FunctionCall (line 110) | func TestTerraform12FunctionCall(t *testing.T) {
function TestTerraform12ListVariable (line 118) | func TestTerraform12ListVariable(t *testing.T) {
function TestTerraform12LocalVariable (line 126) | func TestTerraform12LocalVariable(t *testing.T) {
function TestTerraform12VariablesFromEnvironment (line 134) | func TestTerraform12VariablesFromEnvironment(t *testing.T) {
function TestTerraform12FileFunction (line 144) | func TestTerraform12FileFunction(t *testing.T) {
function TestTerraform12VariablesInDifferentFile (line 151) | func TestTerraform12VariablesInDifferentFile(t *testing.T) {
function TestTerraform12DataLoader (line 169) | func TestTerraform12DataLoader(t *testing.T) {
function TestTerraform12ResourceLineNumber (line 176) | func TestTerraform12ResourceLineNumber(t *testing.T) {
function TestTerraform12ResourceFileName (line 181) | func TestTerraform12ResourceFileName(t *testing.T) {
function TestTerraform12DataLineNumber (line 186) | func TestTerraform12DataLineNumber(t *testing.T) {
function TestTerraform12DataFileName (line 191) | func TestTerraform12DataFileName(t *testing.T) {
function TestTerraform12ProviderLineNumber (line 196) | func TestTerraform12ProviderLineNumber(t *testing.T) {
function TestTerraform12ProviderFileName (line 201) | func TestTerraform12ProviderFileName(t *testing.T) {
function TestTerraform12ModuleLineNumber (line 206) | func TestTerraform12ModuleLineNumber(t *testing.T) {
function TestTerraform12ModuleFileName (line 211) | func TestTerraform12ModuleFileName(t *testing.T) {
function getViolationsString (line 218) | func getViolationsString(violations []assertion.Violation) string {
function TestTerraform12LinterCases (line 236) | func TestTerraform12LinterCases(t *testing.T) {
function TestTerraform12FileFunctionMultiLineContent (line 434) | func TestTerraform12FileFunctionMultiLineContent(t *testing.T) {
function TestTerraform12FileFunctionResourceFileAbsolutePath (line 444) | func TestTerraform12FileFunctionResourceFileAbsolutePath(t *testing.T) {
function TestTerraform12FileFunctionTemplateFileFunction (line 452) | func TestTerraform12FileFunctionTemplateFileFunction(t *testing.T) {
function TestTerraform12FileFunctionTemplateFileForLoop (line 460) | func TestTerraform12FileFunctionTemplateFileForLoop(t *testing.T) {
function TestTerraform12FileFunctionTemplateFileConditional (line 468) | func TestTerraform12FileFunctionTemplateFileConditional(t *testing.T) {
function TestTerraform12FileFunctionReferenceFileAbsoultePath (line 478) | func TestTerraform12FileFunctionReferenceFileAbsoultePath(t *testing.T) {
FILE: linter/tf12parser/attribute.go
type Attribute (line 9) | type Attribute struct
method IsLiteral (line 21) | func (attr *Attribute) IsLiteral() bool {
method Type (line 25) | func (attr *Attribute) Type() cty.Type {
method Value (line 29) | func (attr *Attribute) Value() cty.Value {
method Range (line 40) | func (attr *Attribute) Range() Range {
method Name (line 48) | func (attr *Attribute) Name() string {
function NewAttribute (line 14) | func NewAttribute(attr *hclsyntax.Attribute, ctx *hcl.EvalContext) *Attr...
FILE: linter/tf12parser/block.go
type Block (line 10) | type Block struct
method body (line 52) | func (block *Block) body() *hclsyntax.Body {
method Type (line 56) | func (block *Block) Type() string {
method Labels (line 60) | func (block *Block) Labels() []string {
method Range (line 64) | func (block *Block) Range() Range {
method AllBlocks (line 76) | func (block *Block) AllBlocks() []*Block {
method GetBlock (line 87) | func (block *Block) GetBlock(name string) *Block {
method GetBlocks (line 99) | func (block *Block) GetBlocks(name string) Blocks {
method GetAttributes (line 112) | func (block *Block) GetAttributes() []*Attribute {
method GetAttribute (line 123) | func (block *Block) GetAttribute(name string) *Attribute {
method Name (line 135) | func (block *Block) Name() string {
type Blocks (line 16) | type Blocks
method OfType (line 18) | func (blocks Blocks) OfType(t string) Blocks {
method RemoveDuplicates (line 28) | func (blocks Blocks) RemoveDuplicates() Blocks {
function NewBlock (line 45) | func NewBlock(hclBlock *hcl.Block, ctx *hcl.EvalContext) *Block {
FILE: linter/tf12parser/parser.go
constant maxContextIterations (line 15) | maxContextIterations = 32
type Parser (line 18) | type Parser struct
method ParseMany (line 31) | func (parser *Parser) ParseMany(paths []string) (Blocks, error) {
method ParseDirectory (line 53) | func (parser *Parser) ParseDirectory(path string) (Blocks, error) {
method ParseFile (line 76) | func (parser *Parser) ParseFile(path string) (Blocks, error) {
method parseFile (line 95) | func (parser *Parser) parseFile(file *hcl.File) (hcl.Blocks, error) {
method recursivelyParseDirectory (line 109) | func (parser *Parser) recursivelyParseDirectory(path string) error {
method buildEvaluationContext (line 140) | func (parser *Parser) buildEvaluationContext(blocks hcl.Blocks, path s...
method parseModuleBlock (line 205) | func (parser *Parser) parseModuleBlock(block *hcl.Block, parentContext...
method readValues (line 260) | func (parser *Parser) readValues(ctx *hcl.EvalContext, block *hcl.Bloc...
method getValuesByBlockType (line 278) | func (parser *Parser) getValuesByBlockType(ctx *hcl.EvalContext, block...
function New (line 24) | func New() *Parser {
FILE: linter/tf12parser/parser_test.go
function Test_BasicParsing (line 16) | func Test_BasicParsing(t *testing.T) {
function Test_Modules (line 127) | func Test_Modules(t *testing.T) {
function createTestFile (line 177) | func createTestFile(filename, contents string) string {
function createTestFileWithModule (line 189) | func createTestFileWithModule(contents string, moduleContents string) st...
FILE: linter/tf12parser/range.go
type Range (line 6) | type Range struct
method String (line 13) | func (r *Range) String() string {
FILE: linter/yaml_resource_loader.go
type YAMLResourceLoader (line 9) | type YAMLResourceLoader struct
method Load (line 23) | func (l YAMLResourceLoader) Load(filename string) (FileResources, erro...
method PostLoad (line 58) | func (l YAMLResourceLoader) PostLoad(r FileResources) ([]assertion.Res...
function extractYAMLResourceID (line 13) | func extractYAMLResourceID(expression string, properties interface{}) st...
FILE: linter/yaml_resource_loader_test.go
function TestYAMLLinterValidate (line 9) | func TestYAMLLinterValidate(t *testing.T) {
function TestYAMLLinterSearch (line 25) | func TestYAMLLinterSearch(t *testing.T) {
Condensed preview — 666 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (998K chars).
[
{
"path": ".devcontainer/Dockerfile",
"chars": 49,
"preview": "FROM stelligent/vscode-remote-config-lint:latest\n"
},
{
"path": ".devcontainer/build/Dockerfile",
"chars": 2807,
"preview": "FROM ubuntu:latest\n\n# Avoid warnings by switching to noninteractive\nENV DEBIAN_FRONTEND=noninteractive\n\n# Install packag"
},
{
"path": ".devcontainer/build/dockerhub.sh",
"chars": 701,
"preview": "#!/bin/bash -ex\n\nset +x\nif [[ -z ${DOCKER_ORG} ]];\nthen\n echo DOCKER_ORG must be set in the environment\n exit 1\nfi\nif "
},
{
"path": ".devcontainer/devcontainer.json",
"chars": 2351,
"preview": "{\n\t\"name\": \"config-lint Development\",\n\t\"dockerFile\": \"Dockerfile\",\n\t\"appPort\": 9000,\n\t\"remote.containers.workspaceMountC"
},
{
"path": ".dockerhub/Dockerfile",
"chars": 61,
"preview": "FROM scratch\nCOPY config-lint /\nENTRYPOINT [\"/config-lint\"] \n"
},
{
"path": ".github/workflows/build.yml",
"chars": 818,
"preview": "name: Build\n\non:\n push:\n branches-ignore:\n - 'master'\n tags-ignore:\n - '**'\n paths-ignore:\n - '"
},
{
"path": ".github/workflows/build_and_deploy.yml",
"chars": 1137,
"preview": "name: Build & Deploy\n\non:\n push:\n tags:\n - 'v*.*.*'\n\njobs:\n build:\n runs-on: ubuntu-latest\n steps:\n "
},
{
"path": ".github/workflows/bump_version.yml",
"chars": 635,
"preview": "# Push with a commit message containing `#major` to bump major version\n# and update the major version number here.\n\n# MA"
},
{
"path": ".github/workflows/vscode_remote_development.yml",
"chars": 640,
"preview": "name: VS Code DockerHub Build & Push\n\non:\n push:\n branches:\n - 'master'\n paths:\n - '.devcontainer/**'\n\n"
},
{
"path": ".gitignore",
"chars": 520,
"preview": "# Local dev\ndist/\n.bundle\n.DS_Store\n.vscode/**/*\n.release/\n.idea/\n.DS_Store\n.test/\n*/coverage.out\n*/*packr.go\n**/*.log\n*"
},
{
"path": ".goreleaser.yml",
"chars": 1100,
"preview": "builds:\n-\n main: ./cli\n env:\n - CGO_ENABLED=0\n goos:\n - linux\n - darwin\n - windows\n goarch:\n - 386\n -"
},
{
"path": "CONTRIBUTING.md",
"chars": 3580,
"preview": "# Contributing to config-lint\n\nHelp wanted! We'd love your contributions to config-lint. Please review the following gui"
},
{
"path": "LICENSE.md",
"chars": 1142,
"preview": "MIT License\n\nCopyright (c) 2018-2020 Stelligent\nPortions copyright 2019 Liam Galvin (https://github.com/liamg/tfsec)\n\n\nP"
},
{
"path": "Makefile",
"chars": 2063,
"preview": "# Versioning based on latest git tag.\nVERSION := $(shell git tag -l --sort=creatordate | grep \"^v[0-9]*.[0-9]*.[0-9]*$$\""
},
{
"path": "README.md",
"chars": 2548,
"preview": "[](https://img.shields."
},
{
"path": "assertion/compare.go",
"chars": 1310,
"preview": "package assertion\n\nimport (\n\t\"strconv\"\n\t\"time\"\n)\n\nfunc intCompare(n1 int, n2 int) int {\n\tif n1 < n2 {\n\t\treturn -1\n\t}\n\tif"
},
{
"path": "assertion/compare_test.go",
"chars": 458,
"preview": "package assertion\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestDaysOldForToday(t *test"
},
{
"path": "assertion/contains.go",
"chars": 2434,
"preview": "package assertion\n\nimport (\n\t\"strings\"\n)\n\nfunc interfaceListContains(v []interface{}, key, value string) (MatchResult, e"
},
{
"path": "assertion/contains_test.go",
"chars": 1063,
"preview": "package assertion\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"testing\"\n)\n\n// The non error cases are covered in ma"
},
{
"path": "assertion/expression.go",
"chars": 7080,
"preview": "package assertion\n\nfunc searchAndMatch(expression Expression, resource Resource) (MatchResult, error) {\n\tv, err := Searc"
},
{
"path": "assertion/expression_test.go",
"chars": 10778,
"preview": "package assertion\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n)\n\ntype ExpressionTestCase struct {\n\tRule Rule\n\tResour"
},
{
"path": "assertion/has_properties.go",
"chars": 330,
"preview": "package assertion\n\nimport (\n\t\"strings\"\n)\n\nfunc hasProperties(data interface{}, list string) (MatchResult, error) {\n\tfor "
},
{
"path": "assertion/helper_test.go",
"chars": 1608,
"preview": "package assertion\n\nimport (\n\t\"github.com/ghodss/yaml\"\n\t\"io/ioutil\"\n\t\"testing\"\n)\n\ntype (\n\t// FixtureTestCases is used to "
},
{
"path": "assertion/invoke.go",
"chars": 2859,
"preview": "package assertion\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n)\n\n// InvokeViolation has message "
},
{
"path": "assertion/invoke_test.go",
"chars": 2135,
"preview": "package assertion\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net"
},
{
"path": "assertion/ip_operations.go",
"chars": 1565,
"preview": "package assertion\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nvar rfc1918PrivateCIDRs = []string{\"10.0.0.0/"
},
{
"path": "assertion/ip_operations_test.go",
"chars": 1846,
"preview": "package assertion\n\nimport (\n\t\"testing\"\n)\n\nvar ipTests = []struct {\n\tvalue string\n\tsupernet string\n\texpect"
},
{
"path": "assertion/log.go",
"chars": 485,
"preview": "package assertion\n\nimport \"fmt\"\n\nvar (\n\tisDebug = false\n)\n\n// SetDebug turns verbose logging on or off\nfunc SetDebug(b b"
},
{
"path": "assertion/match.go",
"chars": 4849,
"preview": "package assertion\n\nimport (\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nfunc matches() (MatchResult, error) {\n\treturn MatchResult{Mat"
},
{
"path": "assertion/match_test.go",
"chars": 7312,
"preview": "package assertion\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"testing\"\n)\n\ntype MatchTestCase struct {\n\tSearchResult interface{"
},
{
"path": "assertion/rules.go",
"chars": 6064,
"preview": "package assertion\n\nimport (\n\t\"errors\"\n\t\"github.com/ghodss/yaml\"\n)\n\n// ParseRules converts YAML string content to a Resul"
},
{
"path": "assertion/rules_test.go",
"chars": 7768,
"preview": "package assertion\n\nimport (\n\t\"errors\"\n\t\"testing\"\n)\n\n// TestValueSource provides test values\n\ntype TestValueSource struct"
},
{
"path": "assertion/search.go",
"chars": 288,
"preview": "package assertion\n\nimport (\n\t\"github.com/jmespath/go-jmespath\"\n)\n\n// SearchData applies a JMESPath to a JSON object\nfunc"
},
{
"path": "assertion/testdata/collection-assertions.yaml",
"chars": 5614,
"preview": "---\ndescription: Test collection assertions\ntest_cases:\n\n - name: every_OK\n rule:\n id: COLLECTION\n m"
},
{
"path": "assertion/testdata/conditions.yaml",
"chars": 1433,
"preview": "---\ndescription: Test conditions\ntest_cases:\n\n - name: conditions_false\n rule:\n id: CONDITIONS_1\n message:"
},
{
"path": "assertion/testdata/default-severity.yaml",
"chars": 400,
"preview": "---\ndescription: Test uses default severity\ntest_cases:\n\n - name: default-severity-FAILURE\n rule:\n id: PROPERTI"
},
{
"path": "assertion/testdata/has-properties.yaml",
"chars": 824,
"preview": "---\ndescription: Test has-properties operator\ntest_cases:\n\n - name: has-properties_OK\n rule:\n id: PROPERTIES_1\n"
},
{
"path": "assertion/types.go",
"chars": 3444,
"preview": "package assertion\n\ntype (\n\n\t// Resource describes a resource to be linted\n\tResource struct {\n\t\tID string `cty:\"a"
},
{
"path": "assertion/util.go",
"chars": 4434,
"preview": "package assertion\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\nfunc unquoted(s string) string {\n\tif s[0"
},
{
"path": "assertion/util_test.go",
"chars": 6689,
"preview": "package assertion\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestUnquotedWithoutQuotes(t *testing.T) {\n\tif unquoted(\"Foo\") "
},
{
"path": "assertion/value.go",
"chars": 3322,
"preview": "package assertion\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/s"
},
{
"path": "assertion/value_test.go",
"chars": 988,
"preview": "package assertion\n\nimport (\n\t\"fmt\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\n"
},
{
"path": "cli/app.go",
"chars": 13113,
"preview": "package main\n\n//go:generate packr -v\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"path/filepat"
},
{
"path": "cli/app_test.go",
"chars": 9019,
"preview": "package main\n\nimport (\n\t\"bytes\"\n\t\"testing\"\n\n\t\"github.com/gobuffalo/packr\"\n\t\"github.com/stelligent/config-lint/assertion\""
},
{
"path": "cli/assets/lint-rules.yml",
"chars": 3034,
"preview": "---\nversion: 1\ndescription: Rules for config-lint\ntype: LintRules\nfiles:\n - \"*.yml\"\nrules:\n\n - id: VALID_TYPE\n mess"
},
{
"path": "cli/assets/terraform/aws/api_gateway/api_gateway_domain_name/security_policy/rule.yml",
"chars": 378,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: API_GW_DOMA"
},
{
"path": "cli/assets/terraform/aws/api_gateway/api_gateway_domain_name/security_policy/tests/terraform12/security_policy.tf",
"chars": 874,
"preview": "# Test that an api_gateway_domain_name is using TLS 1.2\n# https://www.terraform.io/docs/providers/aws/r/api_gateway_doma"
},
{
"path": "cli/assets/terraform/aws/api_gateway/api_gateway_domain_name/security_policy/tests/test.yml",
"chars": 226,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/batch/batch_job_definition/aws_secrets/rule.yml",
"chars": 1351,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: BATCH_JOB_A"
},
{
"path": "cli/assets/terraform/aws/batch/batch_job_definition/aws_secrets/tests/terraform12/aws_secrets.tf",
"chars": 2941,
"preview": "# Test that AWS secrets are not being used in batch environment variables\n# https://www.terraform.io/docs/providers/aws/"
},
{
"path": "cli/assets/terraform/aws/batch/batch_job_definition/aws_secrets/tests/test.yml",
"chars": 215,
"preview": "---\nversion: 1\ndescription: Terraform 12 test\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ruleId: BA"
},
{
"path": "cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/rule.yml",
"chars": 418,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: BATCH_DEFIN"
},
{
"path": "cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/tests/terraform11/container_properties_privileged.tf",
"chars": 2206,
"preview": "# Pass\nresource \"aws_batch_job_definition\" \"container_properties_privileged_not_set\" {\n name = \"foo\"\n type = \"containe"
},
{
"path": "cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/tests/terraform12/container_properties_privileged.tf",
"chars": 2206,
"preview": "# Pass\nresource \"aws_batch_job_definition\" \"container_properties_privileged_not_set\" {\n name = \"foo\"\n type = \"containe"
},
{
"path": "cli/assets/terraform/aws/batch/batch_job_definition/container_properties_privileged/tests/test.yml",
"chars": 240,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/rule.yml",
"chars": 672,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CLOUDFRONT_"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/tests/terraform11/custom_origin_config.tf",
"chars": 4753,
"preview": "# Pass\nresource \"aws_cloudfront_distribution\" \"custom_origin_config_not_set\" {\n enabled = true\n\n origin {\n domain_n"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/tests/terraform12/custom_origin_config.tf",
"chars": 5431,
"preview": "# Test that a cloudfront_distribution resource is using OAI or origin_protocol_policy is using https-only\n# https://www."
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/custom_origin_config/tests/test.yml",
"chars": 250,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/rule.yml",
"chars": 358,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CLOUDFRONT_"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/tests/terraform11/logging_config.tf",
"chars": 2065,
"preview": "# Pass\nresource \"aws_cloudfront_distribution\" \"logging_enabled\" {\n enabled = true\n\n origin {\n domain_name = \"http:/"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/tests/terraform12/logging_config.tf",
"chars": 2354,
"preview": "## Setup Helper\nvariable \"test_domain_s3_location\" {\n default = \"http://foo.s3-website-us-east-1.amazonaws.com\"\n}\n\nvari"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/logging_config/tests/test.yml",
"chars": 244,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/minimum_ssl_protocol/rule.yml",
"chars": 501,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CLOUDFRONT_"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/minimum_ssl_protocol/tests/terraform12/minimum_ssl_protocol.tf",
"chars": 3899,
"preview": "# Test that a CloudFront distribution viewer_certificate is using TLS 1.2\n# https://www.terraform.io/docs/providers/aws/"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/minimum_ssl_protocol/tests/test.yml",
"chars": 212,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/rule.yml",
"chars": 553,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CLOUDFRONT_"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/tests/terraform11/viewer_protocol_policy.tf",
"chars": 8561,
"preview": "# Pass\nresource \"aws_cloudfront_distribution\" \"default_cache_behavior_viewer_protocol_policy_set_to_https-only\" {\n enab"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/tests/terraform12/viewer_protocol_policy.tf",
"chars": 8753,
"preview": "## Setup Helper\nvariable \"test_domain_s3_location\" {\n default = \"http://foo.s3-website-us-east-1.amazonaws.com\"\n}\n\nvari"
},
{
"path": "cli/assets/terraform/aws/cloudfront/cloudfront_distribution/viewer_protocol_policy/tests/test.yml",
"chars": 245,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/rule.yml",
"chars": 332,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CLOUDTRAIL_"
},
{
"path": "cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/tests/terraform11/kms_key_id.tf",
"chars": 674,
"preview": "## Setup Helper\nresource \"aws_kms_key\" \"test_key\" {\n enable_key_rotation = true\n}\n\nresource \"aws_s3_bucket\" \"test_bucke"
},
{
"path": "cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/tests/terraform12/kms_key_id.tf",
"chars": 751,
"preview": "## Setup Helper\nvariable \"test_cloudtrail_name\" {\n default = \"foo\"\n}\nresource \"aws_kms_key\" \"test_key\" {\n enable_key_r"
},
{
"path": "cli/assets/terraform/aws/cloudtrail/cloudtrail/kms_key_id/tests/test.yml",
"chars": 234,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/cloudwatch/cloudwatch_log_destination_policy/wildcard_principal/rule.yml",
"chars": 594,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CLOUDWATCH_"
},
{
"path": "cli/assets/terraform/aws/cloudwatch/cloudwatch_log_destination_policy/wildcard_principal/tests/terraform12/wildcard_principal.tf",
"chars": 3246,
"preview": "# Test that CloudWatch log destination policy is not using a wildcard principal\n# https://www.terraform.io/docs/provider"
},
{
"path": "cli/assets/terraform/aws/cloudwatch/cloudwatch_log_destination_policy/wildcard_principal/tests/test.yml",
"chars": 212,
"preview": "---\nversion: 1\ndescription: Terraform 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ruleId: C"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/artifact_encryption/rule.yml",
"chars": 1067,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CODEBUILD_P"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/artifact_encryption/tests/terraform11/artifact_encryption.tf",
"chars": 5153,
"preview": "# Resource required for creating project\nresource \"aws_iam_role\" \"build\" {\n name = \"build\"\n\n assume_role_policy = <<EO"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/artifact_encryption/tests/terraform12/artifact_encryption.tf",
"chars": 5117,
"preview": "# Resource required for creating project\nresource \"aws_iam_role\" \"build\" {\n name = \"build\"\n\n assume_role_policy = <<EO"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/artifact_encryption/tests/test.yml",
"chars": 251,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/project_encryption/rule.yml",
"chars": 339,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CODEBUILD_P"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/project_encryption/tests/terraform11/project_encryption.tf",
"chars": 1858,
"preview": "# Resource required for creating project\nresource \"aws_iam_role\" \"build\" {\n name = \"build\"\n\n assume_role_policy = <<EO"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/project_encryption/tests/terraform12/project_encryption.tf",
"chars": 1842,
"preview": "# Resource required for creating project\nresource \"aws_iam_role\" \"build\" {\n name = \"build\"\n\n assume_role_policy = <<EO"
},
{
"path": "cli/assets/terraform/aws/codebuild/codebuild_project/project_encryption/tests/test.yml",
"chars": 241,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/codepipeline/codepipeline/encryption_key/rule.yml",
"chars": 445,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: CODEPIPELIN"
},
{
"path": "cli/assets/terraform/aws/codepipeline/codepipeline/encryption_key/tests/terraform11/encryption_key.tf",
"chars": 3072,
"preview": "## Setup Helper\nresource \"aws_s3_bucket\" \"test_bucket\" {\n acl = \"private\"\n}\n\nresource \"aws_iam_role\" \"test_role\" {\n as"
},
{
"path": "cli/assets/terraform/aws/codepipeline/codepipeline/encryption_key/tests/terraform12/encryption_key.tf",
"chars": 3042,
"preview": "## Setup Helper\nresource \"aws_s3_bucket\" \"test_bucket\" {\n acl = \"private\"\n}\n\nresource \"aws_iam_role\" \"test_role\" {\n as"
},
{
"path": "cli/assets/terraform/aws/codepipeline/codepipeline/encryption_key/tests/test.yml",
"chars": 236,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/dms/dms_endpoint/endpoint_kms_key/rule.yml",
"chars": 333,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: AWS_DMS_END"
},
{
"path": "cli/assets/terraform/aws/dms/dms_endpoint/endpoint_kms_key/tests/terraform11/kms_key.tf",
"chars": 413,
"preview": "## Setup Helper\nresource \"aws_kms_key\" \"test_key\" {\n enable_key_rotation = true\n}\n\n# Pass\nresource \"aws_dms_endpoint\" \""
},
{
"path": "cli/assets/terraform/aws/dms/dms_endpoint/endpoint_kms_key/tests/terraform12/kms_key.tf",
"chars": 618,
"preview": "## Setup Helper\nvariable \"test_id\" {\n default = \"foo\"\n}\n\nvariable \"test_engine_type\" {\n default = \"source\"\n}\n\nvariable"
},
{
"path": "cli/assets/terraform/aws/dms/dms_endpoint/endpoint_kms_key/tests/test.yml",
"chars": 240,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/documentdb/docdb_cluster/audit_logs/rule.yml",
"chars": 376,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: DOCUMENTDB_"
},
{
"path": "cli/assets/terraform/aws/documentdb/docdb_cluster/audit_logs/tests/terraform12/audit_logs.tf",
"chars": 1505,
"preview": "# Test that DocumentDB audit logging is enabled\n# https://www.terraform.io/docs/providers/aws/r/docdb_cluster.html#enabl"
},
{
"path": "cli/assets/terraform/aws/documentdb/docdb_cluster/audit_logs/tests/test.yml",
"chars": 211,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/documentdb/docdb_cluster/storage_encryption/rule.yml",
"chars": 582,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: DOCUMENTDB_"
},
{
"path": "cli/assets/terraform/aws/documentdb/docdb_cluster/storage_encryption/tests/terraform12/storage_encryption.tf",
"chars": 2065,
"preview": "# Test that a DocumentDB cluster has encryption enabled and a KMS key\n# https://www.terraform.io/docs/providers/aws/r/do"
},
{
"path": "cli/assets/terraform/aws/documentdb/docdb_cluster/storage_encryption/tests/test.yml",
"chars": 322,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/ec2/ami/ebs_block_device_encrypted/rule.yml",
"chars": 387,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: AMI_VOLUMES"
},
{
"path": "cli/assets/terraform/aws/ec2/ami/ebs_block_device_encrypted/tests/terraform11/ebs_block_device_encrypted.tf",
"chars": 532,
"preview": "# Pass\nresource \"aws_ami\" \"ebs_block_device_encrypted_set_to_true\" {\n name = \"foo\"\n\n ebs_block_device {\n device_nam"
},
{
"path": "cli/assets/terraform/aws/ec2/ami/ebs_block_device_encrypted/tests/terraform12/ebs_block_device_encrypted.tf",
"chars": 696,
"preview": "## Setup Helper\nvariable \"test_device\" {\n default = \"/dev/xvda\"\n}\n\nvariable \"test_volume\" {\n default = 8\n}\n\n# Pass\nres"
},
{
"path": "cli/assets/terraform/aws/ec2/ami/ebs_block_device_encrypted/tests/test.yml",
"chars": 234,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/ec2/ami_copy/encrypted/rule.yml",
"chars": 340,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: AMI_COPY_SN"
},
{
"path": "cli/assets/terraform/aws/ec2/ami_copy/encrypted/tests/terraform11/encrypted.tf",
"chars": 528,
"preview": "# Pass\nresource \"aws_ami_copy\" \"encrypted_set_to_true\" {\n name = \"foo\"\n source_ami_id = \"ami-xxxxxxxx"
},
{
"path": "cli/assets/terraform/aws/ec2/ami_copy/encrypted/tests/terraform12/encrypted.tf",
"chars": 654,
"preview": "## Setup Helper\nvariable \"test_ami\" {\n default = \"ami-xxxxxxxx\"\n}\n\nvariable \"test_region\" {\n default = \"us-east-1\"\n}\n\n"
},
{
"path": "cli/assets/terraform/aws/ec2/ami_copy/encrypted/tests/test.yml",
"chars": 241,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/ec2/ebs_volume/encryption/rule.yml",
"chars": 319,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: EBS_VOLUME_"
},
{
"path": "cli/assets/terraform/aws/ec2/ebs_volume/encryption/tests/terraform11/encrypted.tf",
"chars": 417,
"preview": "# Pass\nresource \"aws_ebs_volume\" \"encrypted_set_to_true\" {\n availability_zone = \"us-west-2a\"\n size = 20\n "
},
{
"path": "cli/assets/terraform/aws/ec2/ebs_volume/encryption/tests/terraform12/encrypted.tf",
"chars": 417,
"preview": "# Pass\nresource \"aws_ebs_volume\" \"encrypted_set_to_true\" {\n availability_zone = \"us-west-2a\"\n size = 20\n "
},
{
"path": "cli/assets/terraform/aws/ec2/ebs_volume/encryption/tests/test.yml",
"chars": 234,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/ec2/instance/ebs_block_device_encrypted/rule.yml",
"chars": 748,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: EBS_BLOCK_D"
},
{
"path": "cli/assets/terraform/aws/ec2/instance/ebs_block_device_encrypted/tests/terraform12/ebs_block_device_encrypted.tf",
"chars": 1698,
"preview": "# Test that EBS block device is using encrpytion and specifies a KMS key\n# https://www.terraform.io/docs/providers/aws/r"
},
{
"path": "cli/assets/terraform/aws/ec2/instance/ebs_block_device_encrypted/tests/test.yml",
"chars": 327,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/ecr/ecr_repository_policy/wildcard_principal/rule.yml",
"chars": 535,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ECR_WILDCAR"
},
{
"path": "cli/assets/terraform/aws/ecr/ecr_repository_policy/wildcard_principal/tests/terraform12/wildcard_principal.tf",
"chars": 1852,
"preview": "# Test that ECR allow policy is not using a wildcard principal\n# https://www.terraform.io/docs/providers/aws/r/ecr_repos"
},
{
"path": "cli/assets/terraform/aws/ecr/ecr_repository_policy/wildcard_principal/tests/test.yml",
"chars": 205,
"preview": "---\nversion: 1\ndescription: Terraform 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ruleId: E"
},
{
"path": "cli/assets/terraform/aws/ecs/ecs_task_definition/task_definition_secrets/rule.yml",
"chars": 1343,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ECS_ENVIRON"
},
{
"path": "cli/assets/terraform/aws/ecs/ecs_task_definition/task_definition_secrets/tests/terraform11/secrets.tf",
"chars": 4620,
"preview": "# Pass\nresource \"aws_ecs_task_definition\" \"container_definitions_environment_not_set\" {\n family = \"foo\"\n"
},
{
"path": "cli/assets/terraform/aws/ecs/ecs_task_definition/task_definition_secrets/tests/terraform12/secrets.tf",
"chars": 4620,
"preview": "# Pass\nresource \"aws_ecs_task_definition\" \"container_definitions_environment_not_set\" {\n family = \"foo\"\n"
},
{
"path": "cli/assets/terraform/aws/ecs/ecs_task_definition/task_definition_secrets/tests/test.yml",
"chars": 236,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/efs/efs_file_system/encryption/rule.yml",
"chars": 314,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: EFS_ENCRYPT"
},
{
"path": "cli/assets/terraform/aws/efs/efs_file_system/encryption/tests/terraform11/encrypted.tf",
"chars": 321,
"preview": "# Pass\nresource \"aws_efs_file_system\" \"encrypted_set_to_true\" {\n creation_token = \"foo\"\n encrypted = true\n}\n\n# Fa"
},
{
"path": "cli/assets/terraform/aws/efs/efs_file_system/encryption/tests/terraform12/encrypted.tf",
"chars": 321,
"preview": "# Pass\nresource \"aws_efs_file_system\" \"encrypted_set_to_true\" {\n creation_token = \"foo\"\n encrypted = true\n}\n\n# Fa"
},
{
"path": "cli/assets/terraform/aws/efs/efs_file_system/encryption/tests/test.yml",
"chars": 226,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb/alb_access_logs_enabled/rule.yml",
"chars": 422,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n - id: ALB_ACCESS_L"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb/alb_access_logs_enabled/tests/terraform11/access_logs_enabled.tf",
"chars": 899,
"preview": "## Setup Helper\nresource \"aws_kms_key\" \"test_key\" {\n enable_key_rotation = true\n}\n\nresource \"aws_s3_bucket\" \"test_bucke"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb/alb_access_logs_enabled/tests/terraform12/access_logs_enabled.tf",
"chars": 879,
"preview": "## Setup Helper\nresource \"aws_kms_key\" \"test_key\" {\n enable_key_rotation = true\n}\n\nresource \"aws_s3_bucket\" \"test_bucke"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb/alb_access_logs_enabled/tests/test.yml",
"chars": 228,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb_listener/alb_listener_https/rule.yml",
"chars": 518,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ALB_LISTENE"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb_listener/alb_listener_https/tests/terraform11/https.tf",
"chars": 2801,
"preview": "# Pass\nresource \"aws_alb_listener\" \"listener_secure_https_set\" {\n load_balancer_arn = \"arn:aws:elasticloadbalancing:us-"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb_listener/alb_listener_https/tests/terraform12/https.tf",
"chars": 2630,
"preview": "## Setup Helper\nresource \"aws_vpc\" \"test_vpc\" {\n cidr_block = \"10.0.0.0/16\"\n}\n\nresource \"aws_acm_certificate\" \"test_cer"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/alb_listener/alb_listener_https/tests/test.yml",
"chars": 231,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/elb/access_logs_enabled/rule.yml",
"chars": 850,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ELB_ACCESS_"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/elb/access_logs_enabled/tests/terraform11/access_logs_enabled.tf",
"chars": 1419,
"preview": "# Pass\nresource \"aws_elb\" \"access_logs_set\" {\n availability_zones = [\n \"us-east-1a\",\n \"us-east-1b\",\n \"us-east-"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/elb/access_logs_enabled/tests/terraform12/access_logs_enabled.tf",
"chars": 1419,
"preview": "# Pass\nresource \"aws_elb\" \"access_logs_set\" {\n availability_zones = [\n \"us-east-1a\",\n \"us-east-1b\",\n \"us-east-"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/elb/access_logs_enabled/tests/test.yml",
"chars": 231,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb/access_logs_enabled/rule.yml",
"chars": 418,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: LB_ACCESS_L"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb/access_logs_enabled/tests/terraform11/access_logs_enabled.tf",
"chars": 895,
"preview": "## Setup Helper\nresource \"aws_kms_key\" \"test_key\" {\n enable_key_rotation = true\n}\n\nresource \"aws_s3_bucket\" \"test_bucke"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb/access_logs_enabled/tests/terraform12/access_logs_enabled.tf",
"chars": 875,
"preview": "## Setup Helper\nresource \"aws_kms_key\" \"test_key\" {\n enable_key_rotation = true\n}\n\nresource \"aws_s3_bucket\" \"test_bucke"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb/access_logs_enabled/tests/test.yml",
"chars": 227,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_https/rule.yml",
"chars": 518,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ALB_LISTENE"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_https/tests/terraform11/https.tf",
"chars": 2795,
"preview": "# Pass\nresource \"aws_lb_listener\" \"listener_secure_https_set\" {\n load_balancer_arn = \"arn:aws:elasticloadbalancing:us-e"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_https/tests/terraform12/https.tf",
"chars": 2795,
"preview": "# Pass\nresource \"aws_lb_listener\" \"listener_secure_https_set\" {\n load_balancer_arn = \"arn:aws:elasticloadbalancing:us-e"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_https/tests/test.yml",
"chars": 231,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_ssl_policy/rule.yml",
"chars": 676,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ALB_LISTENE"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_ssl_policy/tests/terraform11/ssl_policy.tf",
"chars": 4594,
"preview": "# Pass\nresource \"aws_lb_listener\" \"ssl_policy_set_to_ELBSecurityPolicy-2016-08\" {\n load_balancer_arn = \"arn:aws:elastic"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_ssl_policy/tests/terraform12/ssl_policy.tf",
"chars": 4594,
"preview": "# Pass\nresource \"aws_lb_listener\" \"ssl_policy_set_to_ELBSecurityPolicy-2016-08\" {\n load_balancer_arn = \"arn:aws:elastic"
},
{
"path": "cli/assets/terraform/aws/elastic_load_balancing/lb_listener/listener_ssl_policy/tests/test.yml",
"chars": 236,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elasticache/elasticache_replication_group/encryption_at_rest/rule.yml",
"chars": 660,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ELASTICACHE"
},
{
"path": "cli/assets/terraform/aws/elasticache/elasticache_replication_group/encryption_at_rest/tests/terraform12/encryption_at_rest.tf",
"chars": 1514,
"preview": "# Test that at-rest encryption is enabled with a KMS key\n# https://www.terraform.io/docs/providers/aws/r/elasticache_rep"
},
{
"path": "cli/assets/terraform/aws/elasticache/elasticache_replication_group/encryption_at_rest/tests/test.yml",
"chars": 329,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elasticache/elasticache_replication_group/encryption_in_transit/rule.yml",
"chars": 396,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ELASTICACHE"
},
{
"path": "cli/assets/terraform/aws/elasticache/elasticache_replication_group/encryption_in_transit/tests/terraform11/encryption_in_transit.tf",
"chars": 918,
"preview": "# Pass\nresource \"aws_elasticache_replication_group\" \"transit_encryption_enabled_is_set_to_true\" {\n replication_group_id"
},
{
"path": "cli/assets/terraform/aws/elasticache/elasticache_replication_group/encryption_in_transit/tests/terraform12/encryption_in_transit.tf",
"chars": 918,
"preview": "# Pass\nresource \"aws_elasticache_replication_group\" \"transit_encryption_enabled_is_set_to_true\" {\n replication_group_id"
},
{
"path": "cli/assets/terraform/aws/elasticache/elasticache_replication_group/encryption_in_transit/tests/test.yml",
"chars": 243,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/encryption_at_rest/rule.yml",
"chars": 478,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ELASTICSEAR"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/encryption_at_rest/tests/terraform12/encryption_at_rest.tf",
"chars": 1075,
"preview": "# Test for encryption at rest options on an elasticsearch domain\n# https://www.terraform.io/docs/providers/aws/r/elastic"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/encryption_at_rest/tests/test.yml",
"chars": 219,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/encryption_node_to_node/rule.yml",
"chars": 505,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ELASTICSEAR"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/encryption_node_to_node/tests/terraform12/encryption_node_to_node.tf",
"chars": 1140,
"preview": "# Test for node to node encryption for an elasticsearch domain\n# https://www.terraform.io/docs/providers/aws/r/elasticse"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/encryption_node_to_node/tests/test.yml",
"chars": 227,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/vpc_subnets/rule.yml",
"chars": 331,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ELASTICSEAR"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/vpc_subnets/tests/terraform12/elasticsearch_vpc.tf",
"chars": 765,
"preview": "# Test that elasticsearch domain is in a VPC using vpc_options\n# https://www.terraform.io/docs/providers/aws/r/elasticse"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/elasticsearch_domain/vpc_subnets/tests/test.yml",
"chars": 207,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/shared/wildcard_principal/rule.yml",
"chars": 629,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: ELASTICSEAR"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/shared/wildcard_principal/tests/terraform12/elasticsearch_domain_policy_wildcard_principal.tf",
"chars": 3167,
"preview": "# Test that an elasticsearch domain policy is not using a wildcard principal\n# https://www.terraform.io/docs/providers/a"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/shared/wildcard_principal/tests/terraform12/elasticsearch_domain_wildcard_principal.tf",
"chars": 2846,
"preview": "# Test that an elasticsearch domain policy is not using a wildcard principal\n# https://www.terraform.io/docs/providers/a"
},
{
"path": "cli/assets/terraform/aws/elasticsearch/shared/wildcard_principal/tests/test.yml",
"chars": 229,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/elastictranscoder/elastictranscoder_pipeline/require_encryption/rule.yml",
"chars": 366,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: TRANSCODER_"
},
{
"path": "cli/assets/terraform/aws/elastictranscoder/elastictranscoder_pipeline/require_encryption/tests/terraform12/require_encryption.tf",
"chars": 1134,
"preview": "# Test that encryption is enabled\n# https://www.terraform.io/docs/providers/aws/r/elastictranscoder_pipeline.html#aws_km"
},
{
"path": "cli/assets/terraform/aws/elastictranscoder/elastictranscoder_pipeline/require_encryption/tests/test.yml",
"chars": 212,
"preview": "---\nversion: 1\ndescription: Terraform 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ruleId: T"
},
{
"path": "cli/assets/terraform/aws/emr/emr_cluster/logging/rule.yml",
"chars": 313,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: AWS_EMR_CLU"
},
{
"path": "cli/assets/terraform/aws/emr/emr_cluster/logging/tests/terraform11/logging.tf",
"chars": 471,
"preview": "## Setup Helper\nresource \"aws_s3_bucket\" \"test_bucket\" {\n}\n\n# Pass\nresource \"aws_emr_cluster\" \"log_uri_is_set\" {\n name "
},
{
"path": "cli/assets/terraform/aws/emr/emr_cluster/logging/tests/terraform12/logging.tf",
"chars": 471,
"preview": "## Setup Helper\nresource \"aws_s3_bucket\" \"test_bucket\" {\n}\n\n# Pass\nresource \"aws_emr_cluster\" \"log_uri_is_set\" {\n name "
},
{
"path": "cli/assets/terraform/aws/emr/emr_cluster/logging/tests/test.yml",
"chars": 236,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/glue/glue_connection/connection_properties/rule.yml",
"chars": 376,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: GLUE_CONNEC"
},
{
"path": "cli/assets/terraform/aws/glue/glue_connection/connection_properties/tests/terraform12/connection_properties.tf",
"chars": 824,
"preview": "# Test that connection_properties is not providing a plaintext password\n# https://www.terraform.io/docs/providers/aws/r/"
},
{
"path": "cli/assets/terraform/aws/glue/glue_connection/connection_properties/tests/test.yml",
"chars": 216,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/iam/iam_group_membership/group_and_users/rule.yml",
"chars": 359,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: IAM_USER_GR"
},
{
"path": "cli/assets/terraform/aws/iam/iam_group_membership/group_and_users/tests/terraform11/group_and_users.tf",
"chars": 858,
"preview": "## Setup Helper\nresource \"aws_iam_group\" \"test_group\" {\n name = \"test-group\"\n}\n\nresource \"aws_iam_user\" \"test_user\" {\n "
},
{
"path": "cli/assets/terraform/aws/iam/iam_group_membership/group_and_users/tests/terraform12/group_and_users.tf",
"chars": 838,
"preview": "## Setup Helper\nresource \"aws_iam_group\" \"test_group\" {\n name = \"test-group\"\n}\n\nresource \"aws_iam_user\" \"test_user\" {\n "
},
{
"path": "cli/assets/terraform/aws/iam/iam_group_membership/group_and_users/tests/test.yml",
"chars": 227,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_action_wildcard/rule.yml",
"chars": 530,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: IAM_POLICY_"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_action_wildcard/tests/terraform11/policy_action_wildcard.tf",
"chars": 901,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_allow_action_without_wildcard\" {\n policy = <<EOF\n{\n \"Version\": \"201"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_action_wildcard/tests/terraform12/policy_action_wildcard.tf",
"chars": 901,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_allow_action_without_wildcard\" {\n policy = <<EOF\n{\n \"Version\": \"201"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_action_wildcard/tests/test.yml",
"chars": 239,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notaction/rule.yml",
"chars": 413,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: IAM_POLICY_"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notaction/tests/terraform11/policy_notaction.tf",
"chars": 606,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_without_notaction\" {\n policy = <<EOF\n{\n \"Version\": \"2012-10-17\",\n "
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notaction/tests/terraform12/policy_notaction.tf",
"chars": 606,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_without_notaction\" {\n policy = <<EOF\n{\n \"Version\": \"2012-10-17\",\n "
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notaction/tests/test.yml",
"chars": 234,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notresource/rule.yml",
"chars": 419,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: IAM_POLICY_"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notresource/tests/terraform11/policy_notresource.tf",
"chars": 588,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_without_notresource\" {\n policy = <<EOF\n{\n \"Version\": \"2012-10-17\",\n"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notresource/tests/terraform12/policy_notresource.tf",
"chars": 588,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_without_notresource\" {\n policy = <<EOF\n{\n \"Version\": \"2012-10-17\",\n"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_notresource/tests/test.yml",
"chars": 236,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_resource_wildcard/rule.yml",
"chars": 536,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: IAM_POLICY_"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_resource_wildcard/tests/terraform11/policy_resource_wildcard.tf",
"chars": 881,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_allow_resource_without_wildcard\" {\n policy = <<EOF\n{\n \"Version\": \"2"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_resource_wildcard/tests/terraform12/policy_resource_wildcard.tf",
"chars": 881,
"preview": "# Pass\nresource \"aws_iam_policy\" \"policy_statement_allow_resource_without_wildcard\" {\n policy = <<EOF\n{\n \"Version\": \"2"
},
{
"path": "cli/assets/terraform/aws/iam/iam_policy/policy_resource_wildcard/tests/test.yml",
"chars": 241,
"preview": "---\nversion: 1\ndescription: Terraform 11 and 12 tests\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\ntests:\n -\n ru"
},
{
"path": "cli/assets/terraform/aws/iam/iam_role/assume_role_policy_action_wildcard/rule.yml",
"chars": 544,
"preview": "---\nversion: 1\ndescription: Terraform rules\ntype: Terraform\nfiles:\n - \"*.tf\"\n - \"*.tfvars\"\nrules:\n\n - id: IAM_ROLE_WI"
}
]
// ... and 466 more files (download for full content)
About this extraction
This page contains the full source code of the stelligent/config-lint GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 666 files (840.3 KB), approximately 283.5k tokens, and a symbol index with 466 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.