From 81d8e54912ccc3df701decb953b7d62c58c9a701 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sat, 24 Apr 2021 12:49:27 -0400 Subject: [PATCH 01/65] [RM-5352] Regula run initial implementation --- .gitignore | 6 + Makefile | 52 ++ VERSION | 1 + cmd/root.go | 21 + cmd/run.go | 69 +++ cmd/version.go | 21 + go.mod | 13 + go.sum | 550 ++++++++++++++++++ lib/fugue/regula/config.rego | 37 -- main.go | 9 + pkg/loader/base/base.go | 32 + pkg/loader/cfn/loader.go | 183 ++++++ pkg/loader/get-loader.go | 30 + pkg/loader/json/detect.go | 1 + pkg/loader/yaml/detect.go | 31 + pkg/rego/runner.go | 166 ++++++ pkg/reporter/base.go | 49 ++ pkg/reporter/get-reporter.go | 15 + pkg/reporter/json.go | 15 + {conftest => rego/conftest}/regula.rego | 0 .../examples}/aws/ec2_t2_only.rego | 0 .../examples}/aws/iam_password_length.rego | 0 .../examples}/aws/tag_all_resources.rego | 0 .../examples}/aws/useast1_only.rego | 0 {lib => rego/lib}/aws/security_group.rego | 0 .../lib}/azure/network_security_group.rego | 0 {lib => rego/lib}/cfn/cloudtrail.rego | 0 {lib => rego/lib}/cfn/lambda_library.rego | 0 {lib => rego/lib}/cfn/nacl_library.rego | 0 {lib => rego/lib}/cfn/s3.rego | 0 .../lib}/cfn/security_group_library.rego | 0 {lib => rego/lib}/fugue.rego | 0 {lib => rego/lib}/fugue/check_report.rego | 0 {lib => rego/lib}/fugue/input_type.rego | 0 {lib => rego/lib}/fugue/regula.rego | 0 {lib => rego/lib}/fugue/resource_view.rego | 0 .../fugue/resource_view/cloudformation.rego | 0 .../lib}/fugue/resource_view/terraform.rego | 0 {lib => rego/lib}/gcp/compute_firewall.rego | 0 {lib => rego/lib}/util/resolve.rego | 0 .../classic_custom_domain_name.rego | 0 .../api_gateway/v2_custom_domain_name.rego | 0 .../rules}/cfn/cloudtrail/cloudwatch.rego | 0 .../rules}/cfn/cloudtrail/encryption.rego | 0 .../rules}/cfn/cloudtrail/log_validation.rego | 0 .../cfn/cloudtrail/s3_access_logging.rego | 0 .../rules}/cfn/cloudtrail/target.rego | 0 .../rules}/cfn/ebs/volume_encryption.rego | 0 .../rules}/cfn/iam/admin_policy.rego | 0 {rules => rego/rules}/cfn/iam/policy.rego | 0 .../rules}/cfn/kms/key_rotation.rego | 0 .../cfn/lambda/function_not_public.rego | 0 .../rules}/cfn/s3/block_public_access.rego | 0 .../s3/cloudtrail_s3_data_logging_read.rego | 0 .../s3/cloudtrail_s3_data_logging_write.rego | 0 {rules => rego/rules}/cfn/s3/encryption.rego | 0 .../rules}/cfn/s3/https_access.rego | 0 .../cfn/vpc/default_security_group.rego | 0 .../rules}/cfn/vpc/flow_logging_enabled.rego | 0 {rules => rego/rules}/cfn/vpc/ingress_22.rego | 0 .../rules}/cfn/vpc/ingress_3389.rego | 0 .../rules}/cfn/vpc/nacl_ingress_22.rego | 0 .../rules}/cfn/vpc/nacl_ingress_3389.rego | 0 .../tf/aws/cloudfront/distribution_https.rego | 0 .../aws/cloudtrail/log_file_validation.rego | 0 .../rules}/tf/aws/ebs/volume_encrypted.rego | 0 .../rules}/tf/aws/iam/admin_policy.rego | 0 .../tf/aws/iam/user_attached_policy.rego | 0 .../rules}/tf/aws/kms/key_rotation.rego | 0 .../rules}/tf/aws/s3/bucket_sse.rego | 0 .../aws/security_group/ingress_anywhere.rego | 0 .../security_group/ingress_anywhere_rdp.rego | 0 .../security_group/ingress_anywhere_ssh.rego | 0 .../rules}/tf/aws/vpc/flow_log.rego | 0 .../network/security_group_no_inbound_22.rego | 0 .../security_group_no_inbound_3389.rego | 0 .../azurerm/sql/firewall_no_inbound_all.rego | 0 .../azurerm/storage/account_deny_access.rego | 0 .../storage/account_microsoft_services.rego | 0 .../storage/account_secure_transfer.rego | 0 .../storage/container_private_access.rego | 0 .../compute/firewall_no_ingress_22.rego | 0 .../compute/firewall_no_ingress_3389.rego | 0 .../compute/subnet_flow_log_enabled.rego | 0 .../compute/subnet_private_google_access.rego | 0 .../tf/google/kms/cryptokey_rotate.rego | 0 {scripts => rego/scripts}/check-naming.sh | 0 .../scripts}/generate-test-inputs.sh | 0 .../tests}/examples/aws/ec2_t2_only_test.rego | 0 .../aws/iam_password_length_test.rego | 0 .../aws/inputs/ec2_t2_only_infra.rego | 0 .../examples/aws/inputs/ec2_t2_only_infra.tf | 0 .../aws/inputs/iam_password_length_infra.rego | 0 .../aws/inputs/iam_password_length_infra.tf | 0 .../aws/inputs/tag_all_resources_infra.rego | 0 .../aws/inputs/tag_all_resources_infra.tf | 0 .../aws/inputs/useast1_only_infra.rego | 0 .../examples/aws/inputs/useast1_only_infra.tf | 0 .../examples/aws/tag_all_resources_test.rego | 0 .../examples/aws/useast1_only_test.rego | 0 .../tests}/lib/cfn/cloudtrail_test.rego | 0 .../lib/fugue_regula_report_01_test.rego | 0 .../lib/fugue_regula_report_02_test.rego | 0 .../lib/fugue_regula_report_03_test.rego | 0 .../lib/fugue_regula_report_04_test.rego | 0 .../tests}/lib/fugue_regula_test.rego | 0 .../lib/fugue_resource_view_01_test.rego | 0 .../lib/fugue_resource_view_02_test.rego | 0 .../lib/fugue_resource_view_03_test.rego | 0 .../lib/fugue_resource_view_04_test.rego | 0 ...gue_resource_view_cloudformation_test.rego | 0 .../lib/fugue_resource_view_modules_test.rego | 0 .../tests}/lib/fugue_resource_view_test.rego | 0 .../tests}/lib/inputs/resource_view_01.tf | 0 .../lib/inputs/resource_view_01_infra.rego | 0 .../lib/inputs/resource_view_01_infra.tf | 0 .../tests}/lib/inputs/resource_view_02.tf | 0 .../lib/inputs/resource_view_02_infra.rego | 0 .../lib/inputs/resource_view_02_infra.tf | 0 .../tests}/lib/inputs/resource_view_03.tf | 0 .../lib/inputs/resource_view_03_infra.rego | 0 .../lib/inputs/resource_view_03_infra.tf | 0 .../lib/inputs/resource_view_04_infra.rego | 0 .../lib/inputs/resource_view_04_infra.tf | 0 .../tests}/lib/util/resolve_test.rego | 0 .../classic_custom_domain_name_test.rego | 0 ...valid_classic_custom_domain_name_infra.cfn | 0 ...alid_classic_custom_domain_name_infra.rego | 0 ...d_classic_custom_domain_name_sam_infra.cfn | 0 ..._classic_custom_domain_name_sam_infra.rego | 0 .../invalid_v2_custom_domain_name_infra.cfn | 0 .../invalid_v2_custom_domain_name_infra.rego | 0 ...nvalid_v2_custom_domain_name_sam_infra.cfn | 0 ...valid_v2_custom_domain_name_sam_infra.rego | 0 ...valid_classic_custom_domain_name_infra.cfn | 0 ...alid_classic_custom_domain_name_infra.rego | 0 ...d_classic_custom_domain_name_sam_infra.cfn | 0 ..._classic_custom_domain_name_sam_infra.rego | 0 .../valid_v2_custom_domain_name_infra.cfn | 0 .../valid_v2_custom_domain_name_infra.rego | 0 .../valid_v2_custom_domain_name_sam_infra.cfn | 0 ...valid_v2_custom_domain_name_sam_infra.rego | 0 .../v2_custom_domain_name_test.rego | 0 .../rules/cfn/cloudtrail/cloudwatch_test.rego | 0 .../rules/cfn/cloudtrail/encryption_test.rego | 0 .../inputs/empty_template_infra.cfn | 0 .../inputs/empty_template_infra.rego | 0 .../inputs/invalid_cloudwatch_infra.cfn | 0 .../inputs/invalid_cloudwatch_infra.rego | 0 .../invalid_cloudwatch_with_valid_infra.cfn | 0 .../invalid_cloudwatch_with_valid_infra.rego | 0 .../inputs/invalid_encryption_infra.cfn | 0 .../inputs/invalid_encryption_infra.rego | 0 .../inputs/invalid_log_validation_infra.cfn | 0 .../inputs/invalid_log_validation_infra.rego | 0 ...nvalid_log_validation_with_valid_infra.cfn | 0 ...valid_log_validation_with_valid_infra.rego | 0 .../invalid_s3_access_logging_infra.cfn | 0 .../invalid_s3_access_logging_infra.rego | 0 .../inputs/invalid_target_public_infra.cfn | 0 .../inputs/invalid_target_public_infra.rego | 0 .../invalid_target_public_write_infra.cfn | 0 .../invalid_target_public_write_infra.rego | 0 .../inputs/valid_cloudwatch_infra.cfn | 0 .../inputs/valid_cloudwatch_infra.rego | 0 .../inputs/valid_encryption_infra.cfn | 0 .../inputs/valid_encryption_infra.rego | 0 .../inputs/valid_log_validation_infra.cfn | 0 .../inputs/valid_log_validation_infra.rego | 0 .../inputs/valid_s3_access_logging_infra.cfn | 0 .../inputs/valid_s3_access_logging_infra.rego | 0 .../inputs/valid_target_full_check_infra.cfn | 0 .../inputs/valid_target_full_check_infra.rego | 0 .../cloudtrail/inputs/valid_target_infra.cfn | 0 .../cloudtrail/inputs/valid_target_infra.rego | 0 .../cfn/cloudtrail/log_validation_test.rego | 0 .../cloudtrail/s3_access_logging_test.rego | 0 .../rules/cfn/cloudtrail/target_test.rego | 0 .../ebs/inputs/volume_encryption_infra.cfn | 0 .../ebs/inputs/volume_encryption_infra.rego | 0 .../rules/cfn/ebs/volume_encryption_test.rego | 0 .../rules/cfn/iam/admin_policy_test.rego | 0 .../cfn/iam/inputs/admin_policy_infra.cfn | 0 .../cfn/iam/inputs/admin_policy_infra.rego | 0 .../rules/cfn/iam/inputs/policy_infra.cfn | 0 .../rules/cfn/iam/inputs/policy_infra.rego | 0 .../tests}/rules/cfn/iam/policy_test.rego | 0 .../cfn/kms/inputs/key_rotation_infra.cfn | 0 .../cfn/kms/inputs/key_rotation_infra.rego | 0 .../rules/cfn/kms/key_rotation_test.rego | 0 .../cfn/lambda/function_not_public_test.rego | 0 .../lambda/inputs/empty_template_infra.cfn | 0 .../lambda/inputs/empty_template_infra.rego | 0 .../invalid_function_not_public_infra.cfn | 0 .../invalid_function_not_public_infra.rego | 0 .../invalid_function_not_public_sam_infra.cfn | 0 ...invalid_function_not_public_sam_infra.rego | 0 ...d_function_not_public_with_valid_infra.cfn | 0 ..._function_not_public_with_valid_infra.rego | 0 ...on_not_public_account_permission_infra.cfn | 0 ...n_not_public_account_permission_infra.rego | 0 ...ot_public_account_permission_sam_infra.cfn | 0 ...t_public_account_permission_sam_infra.rego | 0 .../valid_function_not_public_infra.cfn | 0 .../valid_function_not_public_infra.rego | 0 .../valid_function_not_public_sam_infra.cfn | 0 .../valid_function_not_public_sam_infra.rego | 0 ...on_not_public_service_permission_infra.cfn | 0 ...n_not_public_service_permission_infra.rego | 0 ...ot_public_service_permission_sam_infra.cfn | 0 ...t_public_service_permission_sam_infra.rego | 0 .../cfn/s3/block_public_access_test.rego | 0 .../cloudtrail_s3_data_logging_read_test.rego | 0 ...cloudtrail_s3_data_logging_write_test.rego | 0 .../tests}/rules/cfn/s3/encryption_test.rego | 0 .../rules/cfn/s3/https_access_test.rego | 0 .../cfn/s3/inputs/empty_template_infra.cfn | 0 .../cfn/s3/inputs/empty_template_infra.rego | 0 .../invalid_block_public_access_infra.cfn | 0 .../invalid_block_public_access_infra.rego | 0 ...l_s3_data_logging_all_one_bucket_infra.cfn | 0 ..._s3_data_logging_all_one_bucket_infra.rego | 0 ...dtrail_s3_data_logging_no_trails_infra.cfn | 0 ...trail_s3_data_logging_no_trails_infra.rego | 0 ..._s3_data_logging_read_one_bucket_infra.cfn | 0 ...s3_data_logging_read_one_bucket_infra.rego | 0 ...ata_logging_trail_no_data_events_infra.cfn | 0 ...ta_logging_trail_no_data_events_infra.rego | 0 ...3_data_logging_trail_no_selector_infra.cfn | 0 ..._data_logging_trail_no_selector_infra.rego | 0 ...s3_data_logging_write_one_bucket_infra.cfn | 0 ...3_data_logging_write_one_bucket_infra.rego | 0 ...ogging_write_one_bucket_read_all_infra.cfn | 0 ...gging_write_one_bucket_read_all_infra.rego | 0 .../invalid_encryption_missing_infra.cfn | 0 .../invalid_encryption_missing_infra.rego | 0 .../invalid_encryption_with_valid_infra.cfn | 0 .../invalid_encryption_with_valid_infra.rego | 0 ...valid_https_access_bucket_policy_infra.cfn | 0 ...alid_https_access_bucket_policy_infra.rego | 0 .../cfn/s3/inputs/invalid_missing_infra.cfn | 0 .../cfn/s3/inputs/invalid_missing_infra.rego | 0 .../valid_block_public_access_infra.cfn | 0 .../valid_block_public_access_infra.rego | 0 ..._s3_data_logging_all_all_buckets_infra.cfn | 0 ...s3_data_logging_all_all_buckets_infra.rego | 0 ..._s3_data_logging_all_two_buckets_infra.cfn | 0 ...s3_data_logging_all_two_buckets_infra.rego | 0 ...s3_data_logging_read_all_buckets_infra.cfn | 0 ...3_data_logging_read_all_buckets_infra.rego | 0 ...3_data_logging_write_all_buckets_infra.cfn | 0 ..._data_logging_write_all_buckets_infra.rego | 0 ...ogging_write_one_bucket_read_all_infra.cfn | 0 ...gging_write_one_bucket_read_all_infra.rego | 0 .../cfn/s3/inputs/valid_encryption_infra.cfn | 0 .../cfn/s3/inputs/valid_encryption_infra.rego | 0 ...valid_https_access_bucket_policy_infra.cfn | 0 ...alid_https_access_bucket_policy_infra.rego | 0 .../cfn/vpc/default_security_group_test.rego | 0 .../cfn/vpc/flow_logging_enabled_test.rego | 0 .../tests}/rules/cfn/vpc/ingress_22_test.rego | 0 .../rules/cfn/vpc/ingress_3389_test.rego | 0 .../inputs/default_security_group_infra.cfn | 0 .../inputs/default_security_group_infra.rego | 0 .../vpc/inputs/flow_logging_enabled_infra.cfn | 0 .../inputs/flow_logging_enabled_infra.rego | 0 .../rules/cfn/vpc/inputs/ingress_22_infra.cfn | 0 .../cfn/vpc/inputs/ingress_22_infra.rego | 0 .../cfn/vpc/inputs/ingress_3389_infra.cfn | 0 .../cfn/vpc/inputs/ingress_3389_infra.rego | 0 .../cfn/vpc/inputs/nacl_ingress_22_infra.cfn | 0 .../cfn/vpc/inputs/nacl_ingress_22_infra.rego | 0 .../vpc/inputs/nacl_ingress_3389_infra.cfn | 0 .../vpc/inputs/nacl_ingress_3389_infra.rego | 0 .../rules/cfn/vpc/nacl_ingress_22_test.rego | 0 .../rules/cfn/vpc/nacl_ingress_3389_test.rego | 0 .../cloudfront/distribution_https_test.rego | 0 .../inputs/distribution_https_infra.rego | 0 .../inputs/distribution_https_infra.tf | 0 .../inputs/log_file_validation_infra.rego | 0 .../inputs/log_file_validation_infra.tf | 0 .../cloudtrail/log_file_validation_test.rego | 0 .../ebs/inputs/volume_encrypted_infra.rego | 0 .../aws/ebs/inputs/volume_encrypted_infra.tf | 0 .../tf/aws/ebs/volume_encrypted_test.rego | 0 .../rules/tf/aws/iam/admin_policy_test.rego | 0 .../tf/aws/iam/inputs/admin_policy_infra.rego | 0 .../tf/aws/iam/inputs/admin_policy_infra.tf | 0 .../inputs/user_attached_policy_infra.rego | 0 .../iam/inputs/user_attached_policy_infra.tf | 0 .../tf/aws/iam/user_attached_policy_test.rego | 0 .../tf/aws/kms/inputs/key_rotation_infra.rego | 0 .../tf/aws/kms/inputs/key_rotation_infra.tf | 0 .../rules/tf/aws/kms/key_rotation_test.rego | 0 .../rules/tf/aws/s3/bucket_sse_test.rego | 0 .../tf/aws/s3/inputs/bucket_sse_infra.rego | 0 .../tf/aws/s3/inputs/bucket_sse_infra.tf | 0 .../ingress_anywhere_rdp_test.rego | 0 .../ingress_anywhere_ssh_test.rego | 0 .../security_group/ingress_anywhere_test.rego | 0 .../inputs/ingress_anywhere_infra.rego | 0 .../inputs/ingress_anywhere_infra.tf | 0 .../inputs/ingress_anywhere_rdp_infra.rego | 0 .../inputs/ingress_anywhere_rdp_infra.tf | 0 .../inputs/ingress_anywhere_ssh_infra.rego | 0 .../inputs/ingress_anywhere_ssh_infra.tf | 0 .../rules/tf/aws/vpc/flow_log_test.rego | 0 .../tf/aws/vpc/inputs/flow_log_infra.rego | 0 .../rules/tf/aws/vpc/inputs/flow_log_infra.tf | 0 .../security_group_no_inbound_22_infra.rego | 0 .../security_group_no_inbound_22_infra.tf | 0 .../security_group_no_inbound_3389_infra.rego | 0 .../security_group_no_inbound_3389_infra.tf | 0 .../security_group_no_inbound_22_test.rego | 0 .../security_group_no_inbound_3389_test.rego | 0 .../sql/firewall_no_inbound_all_test.rego | 0 .../inputs/firewall_no_inbound_all_infra.rego | 0 .../inputs/firewall_no_inbound_all_infra.tf | 0 .../storage/account_deny_access_test.rego | 0 .../account_microsoft_services_test.rego | 0 .../storage/account_secure_transfer_test.rego | 0 .../container_private_access_test.rego | 0 .../inputs/account_deny_access_infra.rego | 0 .../inputs/account_deny_access_infra.tf | 0 .../account_microsoft_services_infra.rego | 0 .../account_microsoft_services_infra.tf | 0 .../inputs/account_secure_transfer_infra.rego | 0 .../inputs/account_secure_transfer_infra.tf | 0 .../container_private_access_infra.rego | 0 .../inputs/container_private_access_infra.tf | 0 .../compute/firewall_no_ingress_22_test.rego | 0 .../firewall_no_ingress_3389_test.rego | 0 .../inputs/firewall_no_ingress_22_infra.rego | 0 .../inputs/firewall_no_ingress_22_infra.tf | 0 .../firewall_no_ingress_3389_infra.rego | 0 .../inputs/firewall_no_ingress_3389_infra.tf | 0 .../inputs/subnet_flow_log_enabled_infra.rego | 0 .../inputs/subnet_flow_log_enabled_infra.tf | 0 .../subnet_private_google_access_infra.rego | 0 .../subnet_private_google_access_infra.tf | 0 .../compute/subnet_flow_log_enabled_test.rego | 0 .../subnet_private_google_access_test.rego | 0 .../tf/google/kms/cryptokey_rotate_test.rego | 0 .../kms/inputs/cryptokey_rotate_infra.rego | 0 .../kms/inputs/cryptokey_rotate_infra.tf | 0 regula.png | Bin 408362 -> 0 bytes 346 files changed, 1264 insertions(+), 37 deletions(-) create mode 100644 Makefile create mode 100644 VERSION create mode 100644 cmd/root.go create mode 100644 cmd/run.go create mode 100644 cmd/version.go create mode 100644 go.mod create mode 100644 go.sum delete mode 100644 lib/fugue/regula/config.rego create mode 100644 main.go create mode 100644 pkg/loader/base/base.go create mode 100644 pkg/loader/cfn/loader.go create mode 100644 pkg/loader/get-loader.go create mode 100644 pkg/loader/json/detect.go create mode 100644 pkg/loader/yaml/detect.go create mode 100644 pkg/rego/runner.go create mode 100644 pkg/reporter/base.go create mode 100644 pkg/reporter/get-reporter.go create mode 100644 pkg/reporter/json.go rename {conftest => rego/conftest}/regula.rego (100%) rename {examples => rego/examples}/aws/ec2_t2_only.rego (100%) rename {examples => rego/examples}/aws/iam_password_length.rego (100%) rename {examples => rego/examples}/aws/tag_all_resources.rego (100%) rename {examples => rego/examples}/aws/useast1_only.rego (100%) rename {lib => rego/lib}/aws/security_group.rego (100%) rename {lib => rego/lib}/azure/network_security_group.rego (100%) rename {lib => rego/lib}/cfn/cloudtrail.rego (100%) rename {lib => rego/lib}/cfn/lambda_library.rego (100%) rename {lib => rego/lib}/cfn/nacl_library.rego (100%) rename {lib => rego/lib}/cfn/s3.rego (100%) rename {lib => rego/lib}/cfn/security_group_library.rego (100%) rename {lib => rego/lib}/fugue.rego (100%) rename {lib => rego/lib}/fugue/check_report.rego (100%) rename {lib => rego/lib}/fugue/input_type.rego (100%) rename {lib => rego/lib}/fugue/regula.rego (100%) rename {lib => rego/lib}/fugue/resource_view.rego (100%) rename {lib => rego/lib}/fugue/resource_view/cloudformation.rego (100%) rename {lib => rego/lib}/fugue/resource_view/terraform.rego (100%) rename {lib => rego/lib}/gcp/compute_firewall.rego (100%) rename {lib => rego/lib}/util/resolve.rego (100%) rename {rules => rego/rules}/cfn/api_gateway/classic_custom_domain_name.rego (100%) rename {rules => rego/rules}/cfn/api_gateway/v2_custom_domain_name.rego (100%) rename {rules => rego/rules}/cfn/cloudtrail/cloudwatch.rego (100%) rename {rules => rego/rules}/cfn/cloudtrail/encryption.rego (100%) rename {rules => rego/rules}/cfn/cloudtrail/log_validation.rego (100%) rename {rules => rego/rules}/cfn/cloudtrail/s3_access_logging.rego (100%) rename {rules => rego/rules}/cfn/cloudtrail/target.rego (100%) rename {rules => rego/rules}/cfn/ebs/volume_encryption.rego (100%) rename {rules => rego/rules}/cfn/iam/admin_policy.rego (100%) rename {rules => rego/rules}/cfn/iam/policy.rego (100%) rename {rules => rego/rules}/cfn/kms/key_rotation.rego (100%) rename {rules => rego/rules}/cfn/lambda/function_not_public.rego (100%) rename {rules => rego/rules}/cfn/s3/block_public_access.rego (100%) rename {rules => rego/rules}/cfn/s3/cloudtrail_s3_data_logging_read.rego (100%) rename {rules => rego/rules}/cfn/s3/cloudtrail_s3_data_logging_write.rego (100%) rename {rules => rego/rules}/cfn/s3/encryption.rego (100%) rename {rules => rego/rules}/cfn/s3/https_access.rego (100%) rename {rules => rego/rules}/cfn/vpc/default_security_group.rego (100%) rename {rules => rego/rules}/cfn/vpc/flow_logging_enabled.rego (100%) rename {rules => rego/rules}/cfn/vpc/ingress_22.rego (100%) rename {rules => rego/rules}/cfn/vpc/ingress_3389.rego (100%) rename {rules => rego/rules}/cfn/vpc/nacl_ingress_22.rego (100%) rename {rules => rego/rules}/cfn/vpc/nacl_ingress_3389.rego (100%) rename {rules => rego/rules}/tf/aws/cloudfront/distribution_https.rego (100%) rename {rules => rego/rules}/tf/aws/cloudtrail/log_file_validation.rego (100%) rename {rules => rego/rules}/tf/aws/ebs/volume_encrypted.rego (100%) rename {rules => rego/rules}/tf/aws/iam/admin_policy.rego (100%) rename {rules => rego/rules}/tf/aws/iam/user_attached_policy.rego (100%) rename {rules => rego/rules}/tf/aws/kms/key_rotation.rego (100%) rename {rules => rego/rules}/tf/aws/s3/bucket_sse.rego (100%) rename {rules => rego/rules}/tf/aws/security_group/ingress_anywhere.rego (100%) rename {rules => rego/rules}/tf/aws/security_group/ingress_anywhere_rdp.rego (100%) rename {rules => rego/rules}/tf/aws/security_group/ingress_anywhere_ssh.rego (100%) rename {rules => rego/rules}/tf/aws/vpc/flow_log.rego (100%) rename {rules => rego/rules}/tf/azurerm/network/security_group_no_inbound_22.rego (100%) rename {rules => rego/rules}/tf/azurerm/network/security_group_no_inbound_3389.rego (100%) rename {rules => rego/rules}/tf/azurerm/sql/firewall_no_inbound_all.rego (100%) rename {rules => rego/rules}/tf/azurerm/storage/account_deny_access.rego (100%) rename {rules => rego/rules}/tf/azurerm/storage/account_microsoft_services.rego (100%) rename {rules => rego/rules}/tf/azurerm/storage/account_secure_transfer.rego (100%) rename {rules => rego/rules}/tf/azurerm/storage/container_private_access.rego (100%) rename {rules => rego/rules}/tf/google/compute/firewall_no_ingress_22.rego (100%) rename {rules => rego/rules}/tf/google/compute/firewall_no_ingress_3389.rego (100%) rename {rules => rego/rules}/tf/google/compute/subnet_flow_log_enabled.rego (100%) rename {rules => rego/rules}/tf/google/compute/subnet_private_google_access.rego (100%) rename {rules => rego/rules}/tf/google/kms/cryptokey_rotate.rego (100%) rename {scripts => rego/scripts}/check-naming.sh (100%) rename {scripts => rego/scripts}/generate-test-inputs.sh (100%) rename {tests => rego/tests}/examples/aws/ec2_t2_only_test.rego (100%) rename {tests => rego/tests}/examples/aws/iam_password_length_test.rego (100%) rename {tests => rego/tests}/examples/aws/inputs/ec2_t2_only_infra.rego (100%) rename {tests => rego/tests}/examples/aws/inputs/ec2_t2_only_infra.tf (100%) rename {tests => rego/tests}/examples/aws/inputs/iam_password_length_infra.rego (100%) rename {tests => rego/tests}/examples/aws/inputs/iam_password_length_infra.tf (100%) rename {tests => rego/tests}/examples/aws/inputs/tag_all_resources_infra.rego (100%) rename {tests => rego/tests}/examples/aws/inputs/tag_all_resources_infra.tf (100%) rename {tests => rego/tests}/examples/aws/inputs/useast1_only_infra.rego (100%) rename {tests => rego/tests}/examples/aws/inputs/useast1_only_infra.tf (100%) rename {tests => rego/tests}/examples/aws/tag_all_resources_test.rego (100%) rename {tests => rego/tests}/examples/aws/useast1_only_test.rego (100%) rename {tests => rego/tests}/lib/cfn/cloudtrail_test.rego (100%) rename {tests => rego/tests}/lib/fugue_regula_report_01_test.rego (100%) rename {tests => rego/tests}/lib/fugue_regula_report_02_test.rego (100%) rename {tests => rego/tests}/lib/fugue_regula_report_03_test.rego (100%) rename {tests => rego/tests}/lib/fugue_regula_report_04_test.rego (100%) rename {tests => rego/tests}/lib/fugue_regula_test.rego (100%) rename {tests => rego/tests}/lib/fugue_resource_view_01_test.rego (100%) rename {tests => rego/tests}/lib/fugue_resource_view_02_test.rego (100%) rename {tests => rego/tests}/lib/fugue_resource_view_03_test.rego (100%) rename {tests => rego/tests}/lib/fugue_resource_view_04_test.rego (100%) rename {tests => rego/tests}/lib/fugue_resource_view_cloudformation_test.rego (100%) rename {tests => rego/tests}/lib/fugue_resource_view_modules_test.rego (100%) rename {tests => rego/tests}/lib/fugue_resource_view_test.rego (100%) rename {tests => rego/tests}/lib/inputs/resource_view_01.tf (100%) rename {tests => rego/tests}/lib/inputs/resource_view_01_infra.rego (100%) rename {tests => rego/tests}/lib/inputs/resource_view_01_infra.tf (100%) rename {tests => rego/tests}/lib/inputs/resource_view_02.tf (100%) rename {tests => rego/tests}/lib/inputs/resource_view_02_infra.rego (100%) rename {tests => rego/tests}/lib/inputs/resource_view_02_infra.tf (100%) rename {tests => rego/tests}/lib/inputs/resource_view_03.tf (100%) rename {tests => rego/tests}/lib/inputs/resource_view_03_infra.rego (100%) rename {tests => rego/tests}/lib/inputs/resource_view_03_infra.tf (100%) rename {tests => rego/tests}/lib/inputs/resource_view_04_infra.rego (100%) rename {tests => rego/tests}/lib/inputs/resource_view_04_infra.tf (100%) rename {tests => rego/tests}/lib/util/resolve_test.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/classic_custom_domain_name_test.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/api_gateway/v2_custom_domain_name_test.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/cloudwatch_test.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/encryption_test.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/empty_template_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/empty_template_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_encryption_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_target_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/inputs/valid_target_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/log_validation_test.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/s3_access_logging_test.rego (100%) rename {tests => rego/tests}/rules/cfn/cloudtrail/target_test.rego (100%) rename {tests => rego/tests}/rules/cfn/ebs/inputs/volume_encryption_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/ebs/inputs/volume_encryption_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/ebs/volume_encryption_test.rego (100%) rename {tests => rego/tests}/rules/cfn/iam/admin_policy_test.rego (100%) rename {tests => rego/tests}/rules/cfn/iam/inputs/admin_policy_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/iam/inputs/admin_policy_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/iam/inputs/policy_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/iam/inputs/policy_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/iam/policy_test.rego (100%) rename {tests => rego/tests}/rules/cfn/kms/inputs/key_rotation_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/kms/inputs/key_rotation_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/kms/key_rotation_test.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/function_not_public_test.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/empty_template_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/empty_template_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/invalid_function_not_public_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/block_public_access_test.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/cloudtrail_s3_data_logging_read_test.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/cloudtrail_s3_data_logging_write_test.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/encryption_test.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/https_access_test.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/empty_template_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/empty_template_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_block_public_access_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_encryption_missing_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_missing_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/invalid_missing_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_block_public_access_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_block_public_access_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_encryption_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_encryption_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/default_security_group_test.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/flow_logging_enabled_test.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/ingress_22_test.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/ingress_3389_test.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/default_security_group_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/default_security_group_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/flow_logging_enabled_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/ingress_22_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/ingress_22_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/ingress_3389_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/ingress_3389_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/nacl_ingress_22_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.cfn (100%) rename {tests => rego/tests}/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/nacl_ingress_22_test.rego (100%) rename {tests => rego/tests}/rules/cfn/vpc/nacl_ingress_3389_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/cloudfront/distribution_https_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/cloudtrail/log_file_validation_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/ebs/volume_encrypted_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/iam/admin_policy_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/iam/inputs/admin_policy_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/iam/inputs/admin_policy_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/iam/inputs/user_attached_policy_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/iam/user_attached_policy_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/kms/inputs/key_rotation_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/kms/inputs/key_rotation_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/kms/key_rotation_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/s3/bucket_sse_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/s3/inputs/bucket_sse_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/s3/inputs/bucket_sse_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/ingress_anywhere_rdp_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/ingress_anywhere_ssh_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/ingress_anywhere_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tf (100%) rename {tests => rego/tests}/rules/tf/aws/vpc/flow_log_test.rego (100%) rename {tests => rego/tests}/rules/tf/aws/vpc/inputs/flow_log_infra.rego (100%) rename {tests => rego/tests}/rules/tf/aws/vpc/inputs/flow_log_infra.tf (100%) rename {tests => rego/tests}/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tf (100%) rename {tests => rego/tests}/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tf (100%) rename {tests => rego/tests}/rules/tf/azurerm/network/security_group_no_inbound_22_test.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/network/security_group_no_inbound_3389_test.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/sql/firewall_no_inbound_all_test.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tf (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/account_deny_access_test.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/account_microsoft_services_test.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/account_secure_transfer_test.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/container_private_access_test.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tf (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tf (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tf (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego (100%) rename {tests => rego/tests}/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf (100%) rename {tests => rego/tests}/rules/tf/google/compute/firewall_no_ingress_22_test.rego (100%) rename {tests => rego/tests}/rules/tf/google/compute/firewall_no_ingress_3389_test.rego (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tf (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tf (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tf (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego (100%) rename {tests => rego/tests}/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tf (100%) rename {tests => rego/tests}/rules/tf/google/compute/subnet_flow_log_enabled_test.rego (100%) rename {tests => rego/tests}/rules/tf/google/compute/subnet_private_google_access_test.rego (100%) rename {tests => rego/tests}/rules/tf/google/kms/cryptokey_rotate_test.rego (100%) rename {tests => rego/tests}/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego (100%) rename {tests => rego/tests}/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tf (100%) delete mode 100644 regula.png diff --git a/.gitignore b/.gitignore index f7c1a250..c7411647 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ .terraform/ .DS_Store venv/ +pkg/rego/lib +pkg/rego/rules +regula +regula-* +.vscode/ +.scratch/ diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..ab4c9790 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ +BINARY = regula +INSTALLED_BINARY = /usr/local/bin/$(BINARY) +CLI_SOURCE = $(call rwildcard,./pkg,*.go) +REGO_SOURCE = $(call rwildcard,./rego/lib,*.rego) $(call rwildcard,./rego/rules,*.rego) +GO = GO111MODULE=on go +VERSION = $(shell cat VERSION) +GITCOMMIT = $(shell git rev-parse --short HEAD 2> /dev/null || true) +define LDFLAGS + -X \"github.com/fugue/regula/cmd.Version=$(VERSION)\" \ + -X \"github.com/fugue/regula/cmd.GitCommit=$(GITCOMMIT)\" +endef +CLI_BUILD = $(GO) build -ldflags="$(LDFLAGS) -s -w" +GOLINT = $(shell go env GOPATH)/bin/golint + +$(GOLINT): + $(GO) get -u golang.org/x/lint/golint + +$(BINARY): $(REGO_SOURCE) $(CLI_SOURCE) + $(CLI_BUILD) -v -o $@ + +$(BINARY)-linux-amd64: $(SOURCE) + GOOS=linux GOARCH=amd64 $(CLI_BUILD) -o $@ + +$(BINARY)-darwin-amd64: $(SOURCE) + GOOS=darwin GOARCH=amd64 $(CLI_BUILD) -o $@ + +release: $(BINARY)-linux-amd64 $(BINARY)-darwin-amd64 + +.PHONY: install +install: $(INSTALLED_BINARY) + +.PHONY: clean +clean: + rm -f coverage.out + rm -f $(BINARY) $(BINARY)-linux-amd64 $(BINARY)-darwin-amd64 + +.PHONY: test +test: + $(GO) test -v -cover ./... + +.PHONY: coverage +coverage: + $(GO) test ./... -coverprofile=coverage.out + $(GO) tool cover -html=coverage.out + +.PHONY: lint +lint: + $(GOLINT) ./... + $(GO) vet ./... + +# https://stackoverflow.com/questions/2483182/recursive-wildcards-in-gnu-make +rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) diff --git a/VERSION b/VERSION new file mode 100644 index 00000000..3eefcb9d --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 00000000..f293d576 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,21 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "regula", + Short: "Regula", + Version: fmt.Sprintf("%s, build %s", Version, GitCommit), +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 00000000..a0044940 --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "context" + "fmt" + "os" + + "github.com/fugue/regula/pkg/loader" + "github.com/fugue/regula/pkg/rego" + "github.com/fugue/regula/pkg/reporter" + "github.com/spf13/cobra" +) + +func NewRunCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "run", + Short: "Run Regula rules on inputs", + Run: func(cmd *cobra.Command, args []string) { + ctx := context.TODO() + ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ + Ctx: ctx, + }) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + for _, input := range args { + inputLoader, err := loader.GetLoaderByFileName(input) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + opaInput, err := inputLoader.OpaInput() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + results, err := ruleRunner.Run(opaInput) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + reporterFunc, _ := reporter.GetReporter("json") + + for _, r := range results { + output, err := reporter.ParseRegulaOutput(r) + report, err := reporterFunc(&inputLoader, output) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println(report) + } + } + }, + } + + return cmd +} + +func init() { + rootCmd.AddCommand(NewRunCommand()) +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 00000000..e6f683a5 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,21 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package cmd + +// Default build-time variables. +// These values are overridden via ldflags +var ( + Version = "unknown-version" + GitCommit = "unknown-commit" +) diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..cb7ac1d6 --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module github.com/fugue/regula + +go 1.16 + +require ( + github.com/kr/text v0.2.0 // indirect + github.com/markbates/pkger v0.17.1 + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/open-policy-agent/opa v0.26.0 + github.com/spf13/cobra v1.1.3 + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..fa8e99b7 --- /dev/null +++ b/go.sum @@ -0,0 +1,550 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= +github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= +github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/open-policy-agent/opa v0.26.0 h1:FI0woFdGA73reU8OzSMzgHLFK+XeDMxKIlBpvvpRqDQ= +github.com/open-policy-agent/opa v0.26.0/go.mod h1:iGThTRECCfKQKICueOZkXUi0opN7BR3qiAnIrNHCmlI= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= +github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/wasmerio/go-ext-wasm v0.3.1 h1:G95XP3fE2FszQSwIU+fHPBYzD0Csmd2ef33snQXNA5Q= +github.com/wasmerio/go-ext-wasm v0.3.1/go.mod h1:VGyarTzasuS7k5KhSIGpM3tciSZlkP31Mp9VJTHMMeI= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= +github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20201009032223-96877f285f7e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/lib/fugue/regula/config.rego b/lib/fugue/regula/config.rego deleted file mode 100644 index 7e3acef2..00000000 --- a/lib/fugue/regula/config.rego +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright 2021 Fugue, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# This package provides a stub for users to add their own configuration. This -# way we ensure that the declared sets are incremental. -package fugue.regula.config - -waivers[waiver] { - false - waiver := { - "rule_id": "*", - "rule_name": "*", - "resource_id": "*", - "resource_type": "*", - "filepath": "*", - } -} - -rules[rule] { - false - rule := { - "rule_id": "some_rule_id", - "rule_name": "some_rule_name", - "status": "disabled", - } -} diff --git a/main.go b/main.go new file mode 100644 index 00000000..06b7c2c4 --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/fugue/regula/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/pkg/loader/base/base.go b/pkg/loader/base/base.go new file mode 100644 index 00000000..9261bee4 --- /dev/null +++ b/pkg/loader/base/base.go @@ -0,0 +1,32 @@ +package base + +type InputType int + +const ( + TfPlan InputType = iota + CfnJson + CfnYaml +) + +var InputTypeIds = map[InputType][]string{ + TfPlan: {"tf-plan"}, + CfnJson: {"cfn-json"}, + CfnYaml: {"cfn-yaml"}, +} + +type OpaInput map[string]interface{} + +type Location struct { + Line int + Col int +} + +type Loader interface { + Load(filePath string) error + OpaInput() (OpaInput, error) + Location(attributePath string) (*Location, error) +} + +type Detector interface { + DetectLoader(filePath string) (Loader, error) +} diff --git a/pkg/loader/cfn/loader.go b/pkg/loader/cfn/loader.go new file mode 100644 index 00000000..98804ea9 --- /dev/null +++ b/pkg/loader/cfn/loader.go @@ -0,0 +1,183 @@ +package cfn + +import ( + "fmt" + "io/ioutil" + "strings" + + "github.com/fugue/regula/pkg/loader/base" + "gopkg.in/yaml.v3" +) + +type CfnYamlLoader struct { + template cfnTemplate +} + +func NewCfnYamlLoader(filePath string) (*CfnYamlLoader, error) { + loader := &CfnYamlLoader{} + return loader, loader.Load(filePath) + // if err := ; err != nil { + // return nil, err + // } + // return loader, nil +} + +func (l *CfnYamlLoader) Load(filePath string) error { + fileData, err := ioutil.ReadFile(filePath) + if err != nil { + return fmt.Errorf("Failed to read file: %v", err) + } + + template := &cfnTemplate{} + if err = yaml.Unmarshal(fileData, &template); err != nil { + return fmt.Errorf("Failed to unmarshal CloudFormation YAML file: %v", err) + } + l.template = *template + return nil +} + +func (l *CfnYamlLoader) OpaInput() (base.OpaInput, error) { + return l.template.OpaInput, nil +} + +func (l *CfnYamlLoader) Location(_ string) (*base.Location, error) { + return nil, fmt.Errorf("Location not implemented for this type") +} + +type cfnTemplate struct { + OpaInput base.OpaInput +} + +func (t *cfnTemplate) UnmarshalYAML(node *yaml.Node) error { + opaInput, err := decodeMap(node) + if err != nil { + return err + } + t.OpaInput = opaInput + return nil +} + +func decodeMap(node *yaml.Node) (map[string]interface{}, error) { + if len(node.Content)%2 != 0 { + return nil, fmt.Errorf("Malformed map at line %v, col %v", node.Line, node.Column) + } + + m := map[string]interface{}{} + + for i := 0; i < len(node.Content); i += 2 { + keyNode := node.Content[i] + valNode := node.Content[i+1] + + if keyNode.Kind != yaml.ScalarNode || keyNode.Tag != "!!str" { + return nil, fmt.Errorf("Malformed map key at line %v, col %v", keyNode.Line, keyNode.Column) + } + + var key string + + if err := keyNode.Decode(&key); err != nil { + return nil, fmt.Errorf("Failed to decode map key: %v", err) + } + + val, err := decodeNode(valNode) + + if err != nil { + return nil, fmt.Errorf("Failed to decode map val: %v", err) + } + + m[key] = val + } + + return m, nil +} + +func decodeSeq(node *yaml.Node) ([]interface{}, error) { + s := []interface{}{} + for _, child := range node.Content { + i, err := decodeNode(child) + if err != nil { + return nil, fmt.Errorf("Error decoding sequence item at line %v, col %v", child.Line, child.Column) + } + s = append(s, i) + } + + return s, nil +} + +var intrinsicFns map[string]string = map[string]string{ + "!And": "Fn::And", + "!Base64": "Fn::Base64", + "!Cidr": "Fn::Cidr", + "!Equals": "Fn::Equals", + "!FindInMap": "Fn::FindInMap", + "!GetAtt": "Fn::GetAtt", + "!GetAZs": "Fn::GetAZs", + "!If": "Fn::If", + "!ImportValue": "Fn::ImportValue", + "!Join": "Fn::Join", + "!Not": "Fn::Not", + "!Or": "Fn::Or", + "!Ref": "Ref", + "!Split": "Fn::Split", + "!Sub": "Fn::Sub", + "!Transform": "Fn::Transform", +} + +func decodeIntrinsic(node *yaml.Node, name string) (map[string]interface{}, error) { + if name == "" { + name = strings.Replace(node.Tag, "!", "Fn::", 1) + } + intrinsic := map[string]interface{}{} + switch node.Kind { + case yaml.SequenceNode: + val, err := decodeSeq(node) + if err != nil { + return nil, fmt.Errorf("Failed to decode intrinsic containing sequence: %v", err) + } + intrinsic[name] = val + case yaml.MappingNode: + val, err := decodeMap(node) + if err != nil { + return nil, fmt.Errorf("Failed to decode intrinsic containing map: %v", err) + } + intrinsic[name] = val + default: + var val interface{} + if err := node.Decode(&val); err != nil { + return nil, fmt.Errorf("Failed to decode intrinsic: %v", err) + } + intrinsic[name] = val + } + + return intrinsic, nil +} + +func decodeNode(node *yaml.Node) (interface{}, error) { + switch node.Tag { + case "!!seq": + val, err := decodeSeq(node) + if err != nil { + return nil, fmt.Errorf("Failed to decode map val: %v", err) + } + return val, nil + case "!!map": + val, err := decodeMap(node) + if err != nil { + return nil, fmt.Errorf("Failed to decode map val: %v", err) + } + return val, nil + default: + name, isIntrinsic := intrinsicFns[node.Tag] + if isIntrinsic { + val, err := decodeIntrinsic(node, name) + if err != nil { + return nil, fmt.Errorf("Failed to decode map val: %v", err) + } + return val, nil + } + var val interface{} + if err := node.Decode(&val); err != nil { + return nil, fmt.Errorf("Failed to decode map val: %v", err) + } + return val, nil + } +} diff --git a/pkg/loader/get-loader.go b/pkg/loader/get-loader.go new file mode 100644 index 00000000..d78382df --- /dev/null +++ b/pkg/loader/get-loader.go @@ -0,0 +1,30 @@ +package loader + +import ( + "fmt" + "path/filepath" + + "github.com/fugue/regula/pkg/loader/base" + "github.com/fugue/regula/pkg/loader/cfn" + "github.com/fugue/regula/pkg/loader/yaml" +) + +func GetLoaderByFileName(path string) (base.Loader, error) { + ext := filepath.Ext(path) + + switch ext { + case ".yaml", ".yml": + return yaml.DetectYamlLoader(path) + default: + return nil, fmt.Errorf("Unable to detect file type for file: %s", path) + } +} + +func GetLoaderByInputType(path string, inputType base.InputType) (base.Loader, error) { + switch inputType { + case base.CfnYaml: + return cfn.NewCfnYamlLoader(path) + default: + return nil, fmt.Errorf("Unsupported input type: %s", base.InputTypeIds[inputType]) + } +} diff --git a/pkg/loader/json/detect.go b/pkg/loader/json/detect.go new file mode 100644 index 00000000..a5b981cc --- /dev/null +++ b/pkg/loader/json/detect.go @@ -0,0 +1 @@ +package json diff --git a/pkg/loader/yaml/detect.go b/pkg/loader/yaml/detect.go new file mode 100644 index 00000000..d5ac787d --- /dev/null +++ b/pkg/loader/yaml/detect.go @@ -0,0 +1,31 @@ +package yaml + +import ( + "fmt" + "os" + + yaml "gopkg.in/yaml.v3" + + "github.com/fugue/regula/pkg/loader/base" + "github.com/fugue/regula/pkg/loader/cfn" +) + +type yamlDetector struct { + AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` +} + +func DetectYamlLoader(filePath string) (base.Loader, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + d := &yamlDetector{} + if err := yaml.NewDecoder(f).Decode(d); err != nil { + return nil, err + } + if d.AWSTemplateFormatVersion != "" { + return cfn.NewCfnYamlLoader(filePath) + } + + return nil, fmt.Errorf("Unknown input type in file %s", filePath) +} diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go new file mode 100644 index 00000000..883ae6e4 --- /dev/null +++ b/pkg/rego/runner.go @@ -0,0 +1,166 @@ +package rego + +import ( + "context" + "embed" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/open-policy-agent/opa/rego" +) + +//go:embed lib +var regulaLib embed.FS + +//go:embed rules +var regulaRules embed.FS + +// RuleRunner wraps the logic to load Regula into OPA and evaluate rules +// against an input +type RuleRunner struct { + Ctx context.Context + Query rego.PreparedEvalQuery +} + +// RuleRunnerOptions is a set of options to instantiate a RuleRunner object. +type RuleRunnerOptions struct { + Ctx context.Context + UserOnly bool + Includes []string +} + +var LoadExts map[string]bool = map[string]bool{ + ".rego": true, + ".yaml": true, + ".yml": true, + ".json": true, +} + +func loadModule(fsys fs.FS, path string) (func(r *rego.Rego), error) { + contents, err := fs.ReadFile(fsys, path) + if err != nil { + return nil, err + } + return rego.Module(path, string(contents)), nil +} + +func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { + modules := []func(r *rego.Rego){} + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); !LoadExts[ext] { + return nil + } + module, err := loadModule(fsys, path) + if err != nil { + return err + } + modules = append(modules, module) + return nil + } + + if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { + return nil, err + } + + return modules, nil +} + +func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { + modules := make([]func(r *rego.Rego), len(includes)) + fsys := os.DirFS("") + for _, path := range includes { + info, err := fs.Stat(fsys, path) + if err != nil { + return nil, err + } + if info.IsDir() { + dirModules, err := loadDirectory(fsys, path) + if err != nil { + return nil, err + } + + modules = append(modules, dirModules...) + continue + } + if ext := filepath.Ext(path); !LoadExts[ext] { + return nil, fmt.Errorf("Unsupported file type %v in includes: %v", ext, path) + } + module, err := loadModule(fsys, path) + if err != nil { + return nil, err + } + if module != nil { + modules = append(modules, module) + } + } + + return modules, nil +} + +func loadRegula(userOnly bool) ([]func(r *rego.Rego), error) { + modules, err := loadDirectory(regulaLib, "lib") + if err != nil { + return nil, err + } + + if !userOnly { + rules, err := loadDirectory(regulaRules, "rules") + if err != nil { + return nil, err + } + modules = append(modules, rules...) + } + return modules, nil +} + +// NewRuleRunner instantiates a new RuleRunner +func NewRuleRunner(options *RuleRunnerOptions) (*RuleRunner, error) { + // paths := append(options.RulesDirs, options.LibraryDir) + regula, err := loadRegula(options.UserOnly) + if err != nil { + return nil, fmt.Errorf("Failed to load Regula: %v", err) + } + + includes, err := loadIncludes(options.Includes) + if err != nil { + return nil, fmt.Errorf("Failed to load includes: %v", err) + } + regoFuncs := append( + regula, + append( + includes, + rego.Query("data.fugue.regula.report"), + )..., + ) + query, err := rego.New( + regoFuncs..., + ).PrepareForEval(options.Ctx) + + if err != nil { + return nil, fmt.Errorf("Failed to initialize OPA: %v", err) + } + + return &RuleRunner{ + Ctx: options.Ctx, + Query: query, + }, nil +} + +// Run evaluates rules against an input. +func (r *RuleRunner) Run(input map[string]interface{}) (rego.ResultSet, error) { + results, err := r.Query.Eval(r.Ctx, rego.EvalInput(input)) + + if err != nil { + return nil, fmt.Errorf("Failed to evaluate against input: %v", err) + } + + return results, nil +} diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go new file mode 100644 index 00000000..19c61ebc --- /dev/null +++ b/pkg/reporter/base.go @@ -0,0 +1,49 @@ +package reporter + +import ( + "encoding/json" + + "github.com/fugue/regula/pkg/loader/base" + "github.com/open-policy-agent/opa/rego" +) + +type RegulaOutput struct { + RuleResults []RuleResult `json:"rule_results"` + Summary Summary `json:"summary"` +} + +type RuleResult struct { + Controls []string `json:"controls"` + Filepath string `json:"filepath"` + Platform string `json:"platform"` + Provider string `json:"provider"` + ResourceId string `json:"resource_id"` + ResourceType string `json:"resource_type"` + RuleDescription string `json:"rule_description"` + RuleId string `json:"rule_id"` + RuleMessage string `json:"rule_message"` + RuleName string `json:"rule_name"` + RuleResult string `json:"rule_result"` + RuleSeverity string `json:"rule_severity"` + RuleSummary string `json:"rule_summary"` +} + +type Summary struct { + Filepaths []string `json:"filepaths"` + RuleResults map[string]int `json:"rule_results"` + Severities map[string]int `json:"severities"` +} + +func ParseRegulaOutput(r rego.Result) (*RegulaOutput, error) { + j, err := json.Marshal(r.Expressions[0].Value) + if err != nil { + return nil, err + } + output := &RegulaOutput{} + if err = json.Unmarshal(j, output); err != nil { + return nil, err + } + return output, nil +} + +type Reporter func(l *base.Loader, r *RegulaOutput) (string, error) diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/get-reporter.go new file mode 100644 index 00000000..885bb95c --- /dev/null +++ b/pkg/reporter/get-reporter.go @@ -0,0 +1,15 @@ +package reporter + +import "fmt" + +var Reporters map[string]Reporter = map[string]Reporter{ + "json": JsonReporter, +} + +func GetReporter(name string) (Reporter, error) { + reporter, ok := Reporters[name] + if ok { + return reporter, nil + } + return nil, fmt.Errorf("Unrecognized reporter: %v", name) +} diff --git a/pkg/reporter/json.go b/pkg/reporter/json.go new file mode 100644 index 00000000..52a8d172 --- /dev/null +++ b/pkg/reporter/json.go @@ -0,0 +1,15 @@ +package reporter + +import ( + "encoding/json" + + "github.com/fugue/regula/pkg/loader/base" +) + +func JsonReporter(l *base.Loader, r *RegulaOutput) (string, error) { + j, err := json.MarshalIndent(r, "", " ") + if err != nil { + return "", err + } + return string(j), nil +} diff --git a/conftest/regula.rego b/rego/conftest/regula.rego similarity index 100% rename from conftest/regula.rego rename to rego/conftest/regula.rego diff --git a/examples/aws/ec2_t2_only.rego b/rego/examples/aws/ec2_t2_only.rego similarity index 100% rename from examples/aws/ec2_t2_only.rego rename to rego/examples/aws/ec2_t2_only.rego diff --git a/examples/aws/iam_password_length.rego b/rego/examples/aws/iam_password_length.rego similarity index 100% rename from examples/aws/iam_password_length.rego rename to rego/examples/aws/iam_password_length.rego diff --git a/examples/aws/tag_all_resources.rego b/rego/examples/aws/tag_all_resources.rego similarity index 100% rename from examples/aws/tag_all_resources.rego rename to rego/examples/aws/tag_all_resources.rego diff --git a/examples/aws/useast1_only.rego b/rego/examples/aws/useast1_only.rego similarity index 100% rename from examples/aws/useast1_only.rego rename to rego/examples/aws/useast1_only.rego diff --git a/lib/aws/security_group.rego b/rego/lib/aws/security_group.rego similarity index 100% rename from lib/aws/security_group.rego rename to rego/lib/aws/security_group.rego diff --git a/lib/azure/network_security_group.rego b/rego/lib/azure/network_security_group.rego similarity index 100% rename from lib/azure/network_security_group.rego rename to rego/lib/azure/network_security_group.rego diff --git a/lib/cfn/cloudtrail.rego b/rego/lib/cfn/cloudtrail.rego similarity index 100% rename from lib/cfn/cloudtrail.rego rename to rego/lib/cfn/cloudtrail.rego diff --git a/lib/cfn/lambda_library.rego b/rego/lib/cfn/lambda_library.rego similarity index 100% rename from lib/cfn/lambda_library.rego rename to rego/lib/cfn/lambda_library.rego diff --git a/lib/cfn/nacl_library.rego b/rego/lib/cfn/nacl_library.rego similarity index 100% rename from lib/cfn/nacl_library.rego rename to rego/lib/cfn/nacl_library.rego diff --git a/lib/cfn/s3.rego b/rego/lib/cfn/s3.rego similarity index 100% rename from lib/cfn/s3.rego rename to rego/lib/cfn/s3.rego diff --git a/lib/cfn/security_group_library.rego b/rego/lib/cfn/security_group_library.rego similarity index 100% rename from lib/cfn/security_group_library.rego rename to rego/lib/cfn/security_group_library.rego diff --git a/lib/fugue.rego b/rego/lib/fugue.rego similarity index 100% rename from lib/fugue.rego rename to rego/lib/fugue.rego diff --git a/lib/fugue/check_report.rego b/rego/lib/fugue/check_report.rego similarity index 100% rename from lib/fugue/check_report.rego rename to rego/lib/fugue/check_report.rego diff --git a/lib/fugue/input_type.rego b/rego/lib/fugue/input_type.rego similarity index 100% rename from lib/fugue/input_type.rego rename to rego/lib/fugue/input_type.rego diff --git a/lib/fugue/regula.rego b/rego/lib/fugue/regula.rego similarity index 100% rename from lib/fugue/regula.rego rename to rego/lib/fugue/regula.rego diff --git a/lib/fugue/resource_view.rego b/rego/lib/fugue/resource_view.rego similarity index 100% rename from lib/fugue/resource_view.rego rename to rego/lib/fugue/resource_view.rego diff --git a/lib/fugue/resource_view/cloudformation.rego b/rego/lib/fugue/resource_view/cloudformation.rego similarity index 100% rename from lib/fugue/resource_view/cloudformation.rego rename to rego/lib/fugue/resource_view/cloudformation.rego diff --git a/lib/fugue/resource_view/terraform.rego b/rego/lib/fugue/resource_view/terraform.rego similarity index 100% rename from lib/fugue/resource_view/terraform.rego rename to rego/lib/fugue/resource_view/terraform.rego diff --git a/lib/gcp/compute_firewall.rego b/rego/lib/gcp/compute_firewall.rego similarity index 100% rename from lib/gcp/compute_firewall.rego rename to rego/lib/gcp/compute_firewall.rego diff --git a/lib/util/resolve.rego b/rego/lib/util/resolve.rego similarity index 100% rename from lib/util/resolve.rego rename to rego/lib/util/resolve.rego diff --git a/rules/cfn/api_gateway/classic_custom_domain_name.rego b/rego/rules/cfn/api_gateway/classic_custom_domain_name.rego similarity index 100% rename from rules/cfn/api_gateway/classic_custom_domain_name.rego rename to rego/rules/cfn/api_gateway/classic_custom_domain_name.rego diff --git a/rules/cfn/api_gateway/v2_custom_domain_name.rego b/rego/rules/cfn/api_gateway/v2_custom_domain_name.rego similarity index 100% rename from rules/cfn/api_gateway/v2_custom_domain_name.rego rename to rego/rules/cfn/api_gateway/v2_custom_domain_name.rego diff --git a/rules/cfn/cloudtrail/cloudwatch.rego b/rego/rules/cfn/cloudtrail/cloudwatch.rego similarity index 100% rename from rules/cfn/cloudtrail/cloudwatch.rego rename to rego/rules/cfn/cloudtrail/cloudwatch.rego diff --git a/rules/cfn/cloudtrail/encryption.rego b/rego/rules/cfn/cloudtrail/encryption.rego similarity index 100% rename from rules/cfn/cloudtrail/encryption.rego rename to rego/rules/cfn/cloudtrail/encryption.rego diff --git a/rules/cfn/cloudtrail/log_validation.rego b/rego/rules/cfn/cloudtrail/log_validation.rego similarity index 100% rename from rules/cfn/cloudtrail/log_validation.rego rename to rego/rules/cfn/cloudtrail/log_validation.rego diff --git a/rules/cfn/cloudtrail/s3_access_logging.rego b/rego/rules/cfn/cloudtrail/s3_access_logging.rego similarity index 100% rename from rules/cfn/cloudtrail/s3_access_logging.rego rename to rego/rules/cfn/cloudtrail/s3_access_logging.rego diff --git a/rules/cfn/cloudtrail/target.rego b/rego/rules/cfn/cloudtrail/target.rego similarity index 100% rename from rules/cfn/cloudtrail/target.rego rename to rego/rules/cfn/cloudtrail/target.rego diff --git a/rules/cfn/ebs/volume_encryption.rego b/rego/rules/cfn/ebs/volume_encryption.rego similarity index 100% rename from rules/cfn/ebs/volume_encryption.rego rename to rego/rules/cfn/ebs/volume_encryption.rego diff --git a/rules/cfn/iam/admin_policy.rego b/rego/rules/cfn/iam/admin_policy.rego similarity index 100% rename from rules/cfn/iam/admin_policy.rego rename to rego/rules/cfn/iam/admin_policy.rego diff --git a/rules/cfn/iam/policy.rego b/rego/rules/cfn/iam/policy.rego similarity index 100% rename from rules/cfn/iam/policy.rego rename to rego/rules/cfn/iam/policy.rego diff --git a/rules/cfn/kms/key_rotation.rego b/rego/rules/cfn/kms/key_rotation.rego similarity index 100% rename from rules/cfn/kms/key_rotation.rego rename to rego/rules/cfn/kms/key_rotation.rego diff --git a/rules/cfn/lambda/function_not_public.rego b/rego/rules/cfn/lambda/function_not_public.rego similarity index 100% rename from rules/cfn/lambda/function_not_public.rego rename to rego/rules/cfn/lambda/function_not_public.rego diff --git a/rules/cfn/s3/block_public_access.rego b/rego/rules/cfn/s3/block_public_access.rego similarity index 100% rename from rules/cfn/s3/block_public_access.rego rename to rego/rules/cfn/s3/block_public_access.rego diff --git a/rules/cfn/s3/cloudtrail_s3_data_logging_read.rego b/rego/rules/cfn/s3/cloudtrail_s3_data_logging_read.rego similarity index 100% rename from rules/cfn/s3/cloudtrail_s3_data_logging_read.rego rename to rego/rules/cfn/s3/cloudtrail_s3_data_logging_read.rego diff --git a/rules/cfn/s3/cloudtrail_s3_data_logging_write.rego b/rego/rules/cfn/s3/cloudtrail_s3_data_logging_write.rego similarity index 100% rename from rules/cfn/s3/cloudtrail_s3_data_logging_write.rego rename to rego/rules/cfn/s3/cloudtrail_s3_data_logging_write.rego diff --git a/rules/cfn/s3/encryption.rego b/rego/rules/cfn/s3/encryption.rego similarity index 100% rename from rules/cfn/s3/encryption.rego rename to rego/rules/cfn/s3/encryption.rego diff --git a/rules/cfn/s3/https_access.rego b/rego/rules/cfn/s3/https_access.rego similarity index 100% rename from rules/cfn/s3/https_access.rego rename to rego/rules/cfn/s3/https_access.rego diff --git a/rules/cfn/vpc/default_security_group.rego b/rego/rules/cfn/vpc/default_security_group.rego similarity index 100% rename from rules/cfn/vpc/default_security_group.rego rename to rego/rules/cfn/vpc/default_security_group.rego diff --git a/rules/cfn/vpc/flow_logging_enabled.rego b/rego/rules/cfn/vpc/flow_logging_enabled.rego similarity index 100% rename from rules/cfn/vpc/flow_logging_enabled.rego rename to rego/rules/cfn/vpc/flow_logging_enabled.rego diff --git a/rules/cfn/vpc/ingress_22.rego b/rego/rules/cfn/vpc/ingress_22.rego similarity index 100% rename from rules/cfn/vpc/ingress_22.rego rename to rego/rules/cfn/vpc/ingress_22.rego diff --git a/rules/cfn/vpc/ingress_3389.rego b/rego/rules/cfn/vpc/ingress_3389.rego similarity index 100% rename from rules/cfn/vpc/ingress_3389.rego rename to rego/rules/cfn/vpc/ingress_3389.rego diff --git a/rules/cfn/vpc/nacl_ingress_22.rego b/rego/rules/cfn/vpc/nacl_ingress_22.rego similarity index 100% rename from rules/cfn/vpc/nacl_ingress_22.rego rename to rego/rules/cfn/vpc/nacl_ingress_22.rego diff --git a/rules/cfn/vpc/nacl_ingress_3389.rego b/rego/rules/cfn/vpc/nacl_ingress_3389.rego similarity index 100% rename from rules/cfn/vpc/nacl_ingress_3389.rego rename to rego/rules/cfn/vpc/nacl_ingress_3389.rego diff --git a/rules/tf/aws/cloudfront/distribution_https.rego b/rego/rules/tf/aws/cloudfront/distribution_https.rego similarity index 100% rename from rules/tf/aws/cloudfront/distribution_https.rego rename to rego/rules/tf/aws/cloudfront/distribution_https.rego diff --git a/rules/tf/aws/cloudtrail/log_file_validation.rego b/rego/rules/tf/aws/cloudtrail/log_file_validation.rego similarity index 100% rename from rules/tf/aws/cloudtrail/log_file_validation.rego rename to rego/rules/tf/aws/cloudtrail/log_file_validation.rego diff --git a/rules/tf/aws/ebs/volume_encrypted.rego b/rego/rules/tf/aws/ebs/volume_encrypted.rego similarity index 100% rename from rules/tf/aws/ebs/volume_encrypted.rego rename to rego/rules/tf/aws/ebs/volume_encrypted.rego diff --git a/rules/tf/aws/iam/admin_policy.rego b/rego/rules/tf/aws/iam/admin_policy.rego similarity index 100% rename from rules/tf/aws/iam/admin_policy.rego rename to rego/rules/tf/aws/iam/admin_policy.rego diff --git a/rules/tf/aws/iam/user_attached_policy.rego b/rego/rules/tf/aws/iam/user_attached_policy.rego similarity index 100% rename from rules/tf/aws/iam/user_attached_policy.rego rename to rego/rules/tf/aws/iam/user_attached_policy.rego diff --git a/rules/tf/aws/kms/key_rotation.rego b/rego/rules/tf/aws/kms/key_rotation.rego similarity index 100% rename from rules/tf/aws/kms/key_rotation.rego rename to rego/rules/tf/aws/kms/key_rotation.rego diff --git a/rules/tf/aws/s3/bucket_sse.rego b/rego/rules/tf/aws/s3/bucket_sse.rego similarity index 100% rename from rules/tf/aws/s3/bucket_sse.rego rename to rego/rules/tf/aws/s3/bucket_sse.rego diff --git a/rules/tf/aws/security_group/ingress_anywhere.rego b/rego/rules/tf/aws/security_group/ingress_anywhere.rego similarity index 100% rename from rules/tf/aws/security_group/ingress_anywhere.rego rename to rego/rules/tf/aws/security_group/ingress_anywhere.rego diff --git a/rules/tf/aws/security_group/ingress_anywhere_rdp.rego b/rego/rules/tf/aws/security_group/ingress_anywhere_rdp.rego similarity index 100% rename from rules/tf/aws/security_group/ingress_anywhere_rdp.rego rename to rego/rules/tf/aws/security_group/ingress_anywhere_rdp.rego diff --git a/rules/tf/aws/security_group/ingress_anywhere_ssh.rego b/rego/rules/tf/aws/security_group/ingress_anywhere_ssh.rego similarity index 100% rename from rules/tf/aws/security_group/ingress_anywhere_ssh.rego rename to rego/rules/tf/aws/security_group/ingress_anywhere_ssh.rego diff --git a/rules/tf/aws/vpc/flow_log.rego b/rego/rules/tf/aws/vpc/flow_log.rego similarity index 100% rename from rules/tf/aws/vpc/flow_log.rego rename to rego/rules/tf/aws/vpc/flow_log.rego diff --git a/rules/tf/azurerm/network/security_group_no_inbound_22.rego b/rego/rules/tf/azurerm/network/security_group_no_inbound_22.rego similarity index 100% rename from rules/tf/azurerm/network/security_group_no_inbound_22.rego rename to rego/rules/tf/azurerm/network/security_group_no_inbound_22.rego diff --git a/rules/tf/azurerm/network/security_group_no_inbound_3389.rego b/rego/rules/tf/azurerm/network/security_group_no_inbound_3389.rego similarity index 100% rename from rules/tf/azurerm/network/security_group_no_inbound_3389.rego rename to rego/rules/tf/azurerm/network/security_group_no_inbound_3389.rego diff --git a/rules/tf/azurerm/sql/firewall_no_inbound_all.rego b/rego/rules/tf/azurerm/sql/firewall_no_inbound_all.rego similarity index 100% rename from rules/tf/azurerm/sql/firewall_no_inbound_all.rego rename to rego/rules/tf/azurerm/sql/firewall_no_inbound_all.rego diff --git a/rules/tf/azurerm/storage/account_deny_access.rego b/rego/rules/tf/azurerm/storage/account_deny_access.rego similarity index 100% rename from rules/tf/azurerm/storage/account_deny_access.rego rename to rego/rules/tf/azurerm/storage/account_deny_access.rego diff --git a/rules/tf/azurerm/storage/account_microsoft_services.rego b/rego/rules/tf/azurerm/storage/account_microsoft_services.rego similarity index 100% rename from rules/tf/azurerm/storage/account_microsoft_services.rego rename to rego/rules/tf/azurerm/storage/account_microsoft_services.rego diff --git a/rules/tf/azurerm/storage/account_secure_transfer.rego b/rego/rules/tf/azurerm/storage/account_secure_transfer.rego similarity index 100% rename from rules/tf/azurerm/storage/account_secure_transfer.rego rename to rego/rules/tf/azurerm/storage/account_secure_transfer.rego diff --git a/rules/tf/azurerm/storage/container_private_access.rego b/rego/rules/tf/azurerm/storage/container_private_access.rego similarity index 100% rename from rules/tf/azurerm/storage/container_private_access.rego rename to rego/rules/tf/azurerm/storage/container_private_access.rego diff --git a/rules/tf/google/compute/firewall_no_ingress_22.rego b/rego/rules/tf/google/compute/firewall_no_ingress_22.rego similarity index 100% rename from rules/tf/google/compute/firewall_no_ingress_22.rego rename to rego/rules/tf/google/compute/firewall_no_ingress_22.rego diff --git a/rules/tf/google/compute/firewall_no_ingress_3389.rego b/rego/rules/tf/google/compute/firewall_no_ingress_3389.rego similarity index 100% rename from rules/tf/google/compute/firewall_no_ingress_3389.rego rename to rego/rules/tf/google/compute/firewall_no_ingress_3389.rego diff --git a/rules/tf/google/compute/subnet_flow_log_enabled.rego b/rego/rules/tf/google/compute/subnet_flow_log_enabled.rego similarity index 100% rename from rules/tf/google/compute/subnet_flow_log_enabled.rego rename to rego/rules/tf/google/compute/subnet_flow_log_enabled.rego diff --git a/rules/tf/google/compute/subnet_private_google_access.rego b/rego/rules/tf/google/compute/subnet_private_google_access.rego similarity index 100% rename from rules/tf/google/compute/subnet_private_google_access.rego rename to rego/rules/tf/google/compute/subnet_private_google_access.rego diff --git a/rules/tf/google/kms/cryptokey_rotate.rego b/rego/rules/tf/google/kms/cryptokey_rotate.rego similarity index 100% rename from rules/tf/google/kms/cryptokey_rotate.rego rename to rego/rules/tf/google/kms/cryptokey_rotate.rego diff --git a/scripts/check-naming.sh b/rego/scripts/check-naming.sh similarity index 100% rename from scripts/check-naming.sh rename to rego/scripts/check-naming.sh diff --git a/scripts/generate-test-inputs.sh b/rego/scripts/generate-test-inputs.sh similarity index 100% rename from scripts/generate-test-inputs.sh rename to rego/scripts/generate-test-inputs.sh diff --git a/tests/examples/aws/ec2_t2_only_test.rego b/rego/tests/examples/aws/ec2_t2_only_test.rego similarity index 100% rename from tests/examples/aws/ec2_t2_only_test.rego rename to rego/tests/examples/aws/ec2_t2_only_test.rego diff --git a/tests/examples/aws/iam_password_length_test.rego b/rego/tests/examples/aws/iam_password_length_test.rego similarity index 100% rename from tests/examples/aws/iam_password_length_test.rego rename to rego/tests/examples/aws/iam_password_length_test.rego diff --git a/tests/examples/aws/inputs/ec2_t2_only_infra.rego b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego similarity index 100% rename from tests/examples/aws/inputs/ec2_t2_only_infra.rego rename to rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego diff --git a/tests/examples/aws/inputs/ec2_t2_only_infra.tf b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.tf similarity index 100% rename from tests/examples/aws/inputs/ec2_t2_only_infra.tf rename to rego/tests/examples/aws/inputs/ec2_t2_only_infra.tf diff --git a/tests/examples/aws/inputs/iam_password_length_infra.rego b/rego/tests/examples/aws/inputs/iam_password_length_infra.rego similarity index 100% rename from tests/examples/aws/inputs/iam_password_length_infra.rego rename to rego/tests/examples/aws/inputs/iam_password_length_infra.rego diff --git a/tests/examples/aws/inputs/iam_password_length_infra.tf b/rego/tests/examples/aws/inputs/iam_password_length_infra.tf similarity index 100% rename from tests/examples/aws/inputs/iam_password_length_infra.tf rename to rego/tests/examples/aws/inputs/iam_password_length_infra.tf diff --git a/tests/examples/aws/inputs/tag_all_resources_infra.rego b/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego similarity index 100% rename from tests/examples/aws/inputs/tag_all_resources_infra.rego rename to rego/tests/examples/aws/inputs/tag_all_resources_infra.rego diff --git a/tests/examples/aws/inputs/tag_all_resources_infra.tf b/rego/tests/examples/aws/inputs/tag_all_resources_infra.tf similarity index 100% rename from tests/examples/aws/inputs/tag_all_resources_infra.tf rename to rego/tests/examples/aws/inputs/tag_all_resources_infra.tf diff --git a/tests/examples/aws/inputs/useast1_only_infra.rego b/rego/tests/examples/aws/inputs/useast1_only_infra.rego similarity index 100% rename from tests/examples/aws/inputs/useast1_only_infra.rego rename to rego/tests/examples/aws/inputs/useast1_only_infra.rego diff --git a/tests/examples/aws/inputs/useast1_only_infra.tf b/rego/tests/examples/aws/inputs/useast1_only_infra.tf similarity index 100% rename from tests/examples/aws/inputs/useast1_only_infra.tf rename to rego/tests/examples/aws/inputs/useast1_only_infra.tf diff --git a/tests/examples/aws/tag_all_resources_test.rego b/rego/tests/examples/aws/tag_all_resources_test.rego similarity index 100% rename from tests/examples/aws/tag_all_resources_test.rego rename to rego/tests/examples/aws/tag_all_resources_test.rego diff --git a/tests/examples/aws/useast1_only_test.rego b/rego/tests/examples/aws/useast1_only_test.rego similarity index 100% rename from tests/examples/aws/useast1_only_test.rego rename to rego/tests/examples/aws/useast1_only_test.rego diff --git a/tests/lib/cfn/cloudtrail_test.rego b/rego/tests/lib/cfn/cloudtrail_test.rego similarity index 100% rename from tests/lib/cfn/cloudtrail_test.rego rename to rego/tests/lib/cfn/cloudtrail_test.rego diff --git a/tests/lib/fugue_regula_report_01_test.rego b/rego/tests/lib/fugue_regula_report_01_test.rego similarity index 100% rename from tests/lib/fugue_regula_report_01_test.rego rename to rego/tests/lib/fugue_regula_report_01_test.rego diff --git a/tests/lib/fugue_regula_report_02_test.rego b/rego/tests/lib/fugue_regula_report_02_test.rego similarity index 100% rename from tests/lib/fugue_regula_report_02_test.rego rename to rego/tests/lib/fugue_regula_report_02_test.rego diff --git a/tests/lib/fugue_regula_report_03_test.rego b/rego/tests/lib/fugue_regula_report_03_test.rego similarity index 100% rename from tests/lib/fugue_regula_report_03_test.rego rename to rego/tests/lib/fugue_regula_report_03_test.rego diff --git a/tests/lib/fugue_regula_report_04_test.rego b/rego/tests/lib/fugue_regula_report_04_test.rego similarity index 100% rename from tests/lib/fugue_regula_report_04_test.rego rename to rego/tests/lib/fugue_regula_report_04_test.rego diff --git a/tests/lib/fugue_regula_test.rego b/rego/tests/lib/fugue_regula_test.rego similarity index 100% rename from tests/lib/fugue_regula_test.rego rename to rego/tests/lib/fugue_regula_test.rego diff --git a/tests/lib/fugue_resource_view_01_test.rego b/rego/tests/lib/fugue_resource_view_01_test.rego similarity index 100% rename from tests/lib/fugue_resource_view_01_test.rego rename to rego/tests/lib/fugue_resource_view_01_test.rego diff --git a/tests/lib/fugue_resource_view_02_test.rego b/rego/tests/lib/fugue_resource_view_02_test.rego similarity index 100% rename from tests/lib/fugue_resource_view_02_test.rego rename to rego/tests/lib/fugue_resource_view_02_test.rego diff --git a/tests/lib/fugue_resource_view_03_test.rego b/rego/tests/lib/fugue_resource_view_03_test.rego similarity index 100% rename from tests/lib/fugue_resource_view_03_test.rego rename to rego/tests/lib/fugue_resource_view_03_test.rego diff --git a/tests/lib/fugue_resource_view_04_test.rego b/rego/tests/lib/fugue_resource_view_04_test.rego similarity index 100% rename from tests/lib/fugue_resource_view_04_test.rego rename to rego/tests/lib/fugue_resource_view_04_test.rego diff --git a/tests/lib/fugue_resource_view_cloudformation_test.rego b/rego/tests/lib/fugue_resource_view_cloudformation_test.rego similarity index 100% rename from tests/lib/fugue_resource_view_cloudformation_test.rego rename to rego/tests/lib/fugue_resource_view_cloudformation_test.rego diff --git a/tests/lib/fugue_resource_view_modules_test.rego b/rego/tests/lib/fugue_resource_view_modules_test.rego similarity index 100% rename from tests/lib/fugue_resource_view_modules_test.rego rename to rego/tests/lib/fugue_resource_view_modules_test.rego diff --git a/tests/lib/fugue_resource_view_test.rego b/rego/tests/lib/fugue_resource_view_test.rego similarity index 100% rename from tests/lib/fugue_resource_view_test.rego rename to rego/tests/lib/fugue_resource_view_test.rego diff --git a/tests/lib/inputs/resource_view_01.tf b/rego/tests/lib/inputs/resource_view_01.tf similarity index 100% rename from tests/lib/inputs/resource_view_01.tf rename to rego/tests/lib/inputs/resource_view_01.tf diff --git a/tests/lib/inputs/resource_view_01_infra.rego b/rego/tests/lib/inputs/resource_view_01_infra.rego similarity index 100% rename from tests/lib/inputs/resource_view_01_infra.rego rename to rego/tests/lib/inputs/resource_view_01_infra.rego diff --git a/tests/lib/inputs/resource_view_01_infra.tf b/rego/tests/lib/inputs/resource_view_01_infra.tf similarity index 100% rename from tests/lib/inputs/resource_view_01_infra.tf rename to rego/tests/lib/inputs/resource_view_01_infra.tf diff --git a/tests/lib/inputs/resource_view_02.tf b/rego/tests/lib/inputs/resource_view_02.tf similarity index 100% rename from tests/lib/inputs/resource_view_02.tf rename to rego/tests/lib/inputs/resource_view_02.tf diff --git a/tests/lib/inputs/resource_view_02_infra.rego b/rego/tests/lib/inputs/resource_view_02_infra.rego similarity index 100% rename from tests/lib/inputs/resource_view_02_infra.rego rename to rego/tests/lib/inputs/resource_view_02_infra.rego diff --git a/tests/lib/inputs/resource_view_02_infra.tf b/rego/tests/lib/inputs/resource_view_02_infra.tf similarity index 100% rename from tests/lib/inputs/resource_view_02_infra.tf rename to rego/tests/lib/inputs/resource_view_02_infra.tf diff --git a/tests/lib/inputs/resource_view_03.tf b/rego/tests/lib/inputs/resource_view_03.tf similarity index 100% rename from tests/lib/inputs/resource_view_03.tf rename to rego/tests/lib/inputs/resource_view_03.tf diff --git a/tests/lib/inputs/resource_view_03_infra.rego b/rego/tests/lib/inputs/resource_view_03_infra.rego similarity index 100% rename from tests/lib/inputs/resource_view_03_infra.rego rename to rego/tests/lib/inputs/resource_view_03_infra.rego diff --git a/tests/lib/inputs/resource_view_03_infra.tf b/rego/tests/lib/inputs/resource_view_03_infra.tf similarity index 100% rename from tests/lib/inputs/resource_view_03_infra.tf rename to rego/tests/lib/inputs/resource_view_03_infra.tf diff --git a/tests/lib/inputs/resource_view_04_infra.rego b/rego/tests/lib/inputs/resource_view_04_infra.rego similarity index 100% rename from tests/lib/inputs/resource_view_04_infra.rego rename to rego/tests/lib/inputs/resource_view_04_infra.rego diff --git a/tests/lib/inputs/resource_view_04_infra.tf b/rego/tests/lib/inputs/resource_view_04_infra.tf similarity index 100% rename from tests/lib/inputs/resource_view_04_infra.tf rename to rego/tests/lib/inputs/resource_view_04_infra.tf diff --git a/tests/lib/util/resolve_test.rego b/rego/tests/lib/util/resolve_test.rego similarity index 100% rename from tests/lib/util/resolve_test.rego rename to rego/tests/lib/util/resolve_test.rego diff --git a/tests/rules/cfn/api_gateway/classic_custom_domain_name_test.rego b/rego/tests/rules/cfn/api_gateway/classic_custom_domain_name_test.rego similarity index 100% rename from tests/rules/cfn/api_gateway/classic_custom_domain_name_test.rego rename to rego/tests/rules/cfn/api_gateway/classic_custom_domain_name_test.rego diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego diff --git a/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego diff --git a/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego diff --git a/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego diff --git a/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.cfn b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.cfn rename to rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.cfn diff --git a/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego similarity index 100% rename from tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego rename to rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego diff --git a/tests/rules/cfn/api_gateway/v2_custom_domain_name_test.rego b/rego/tests/rules/cfn/api_gateway/v2_custom_domain_name_test.rego similarity index 100% rename from tests/rules/cfn/api_gateway/v2_custom_domain_name_test.rego rename to rego/tests/rules/cfn/api_gateway/v2_custom_domain_name_test.rego diff --git a/tests/rules/cfn/cloudtrail/cloudwatch_test.rego b/rego/tests/rules/cfn/cloudtrail/cloudwatch_test.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/cloudwatch_test.rego rename to rego/tests/rules/cfn/cloudtrail/cloudwatch_test.rego diff --git a/tests/rules/cfn/cloudtrail/encryption_test.rego b/rego/tests/rules/cfn/cloudtrail/encryption_test.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/encryption_test.rego rename to rego/tests/rules/cfn/cloudtrail/encryption_test.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/empty_template_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.cfn b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.cfn similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_target_infra.cfn rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.cfn diff --git a/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego rename to rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego diff --git a/tests/rules/cfn/cloudtrail/log_validation_test.rego b/rego/tests/rules/cfn/cloudtrail/log_validation_test.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/log_validation_test.rego rename to rego/tests/rules/cfn/cloudtrail/log_validation_test.rego diff --git a/tests/rules/cfn/cloudtrail/s3_access_logging_test.rego b/rego/tests/rules/cfn/cloudtrail/s3_access_logging_test.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/s3_access_logging_test.rego rename to rego/tests/rules/cfn/cloudtrail/s3_access_logging_test.rego diff --git a/tests/rules/cfn/cloudtrail/target_test.rego b/rego/tests/rules/cfn/cloudtrail/target_test.rego similarity index 100% rename from tests/rules/cfn/cloudtrail/target_test.rego rename to rego/tests/rules/cfn/cloudtrail/target_test.rego diff --git a/tests/rules/cfn/ebs/inputs/volume_encryption_infra.cfn b/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.cfn similarity index 100% rename from tests/rules/cfn/ebs/inputs/volume_encryption_infra.cfn rename to rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.cfn diff --git a/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego b/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego similarity index 100% rename from tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego rename to rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego diff --git a/tests/rules/cfn/ebs/volume_encryption_test.rego b/rego/tests/rules/cfn/ebs/volume_encryption_test.rego similarity index 100% rename from tests/rules/cfn/ebs/volume_encryption_test.rego rename to rego/tests/rules/cfn/ebs/volume_encryption_test.rego diff --git a/tests/rules/cfn/iam/admin_policy_test.rego b/rego/tests/rules/cfn/iam/admin_policy_test.rego similarity index 100% rename from tests/rules/cfn/iam/admin_policy_test.rego rename to rego/tests/rules/cfn/iam/admin_policy_test.rego diff --git a/tests/rules/cfn/iam/inputs/admin_policy_infra.cfn b/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.cfn similarity index 100% rename from tests/rules/cfn/iam/inputs/admin_policy_infra.cfn rename to rego/tests/rules/cfn/iam/inputs/admin_policy_infra.cfn diff --git a/tests/rules/cfn/iam/inputs/admin_policy_infra.rego b/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego similarity index 100% rename from tests/rules/cfn/iam/inputs/admin_policy_infra.rego rename to rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego diff --git a/tests/rules/cfn/iam/inputs/policy_infra.cfn b/rego/tests/rules/cfn/iam/inputs/policy_infra.cfn similarity index 100% rename from tests/rules/cfn/iam/inputs/policy_infra.cfn rename to rego/tests/rules/cfn/iam/inputs/policy_infra.cfn diff --git a/tests/rules/cfn/iam/inputs/policy_infra.rego b/rego/tests/rules/cfn/iam/inputs/policy_infra.rego similarity index 100% rename from tests/rules/cfn/iam/inputs/policy_infra.rego rename to rego/tests/rules/cfn/iam/inputs/policy_infra.rego diff --git a/tests/rules/cfn/iam/policy_test.rego b/rego/tests/rules/cfn/iam/policy_test.rego similarity index 100% rename from tests/rules/cfn/iam/policy_test.rego rename to rego/tests/rules/cfn/iam/policy_test.rego diff --git a/tests/rules/cfn/kms/inputs/key_rotation_infra.cfn b/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.cfn similarity index 100% rename from tests/rules/cfn/kms/inputs/key_rotation_infra.cfn rename to rego/tests/rules/cfn/kms/inputs/key_rotation_infra.cfn diff --git a/tests/rules/cfn/kms/inputs/key_rotation_infra.rego b/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego similarity index 100% rename from tests/rules/cfn/kms/inputs/key_rotation_infra.rego rename to rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego diff --git a/tests/rules/cfn/kms/key_rotation_test.rego b/rego/tests/rules/cfn/kms/key_rotation_test.rego similarity index 100% rename from tests/rules/cfn/kms/key_rotation_test.rego rename to rego/tests/rules/cfn/kms/key_rotation_test.rego diff --git a/tests/rules/cfn/lambda/function_not_public_test.rego b/rego/tests/rules/cfn/lambda/function_not_public_test.rego similarity index 100% rename from tests/rules/cfn/lambda/function_not_public_test.rego rename to rego/tests/rules/cfn/lambda/function_not_public_test.rego diff --git a/tests/rules/cfn/lambda/inputs/empty_template_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/empty_template_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/empty_template_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/empty_template_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.cfn b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.cfn similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.cfn rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.cfn diff --git a/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego similarity index 100% rename from tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego rename to rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego diff --git a/tests/rules/cfn/s3/block_public_access_test.rego b/rego/tests/rules/cfn/s3/block_public_access_test.rego similarity index 100% rename from tests/rules/cfn/s3/block_public_access_test.rego rename to rego/tests/rules/cfn/s3/block_public_access_test.rego diff --git a/tests/rules/cfn/s3/cloudtrail_s3_data_logging_read_test.rego b/rego/tests/rules/cfn/s3/cloudtrail_s3_data_logging_read_test.rego similarity index 100% rename from tests/rules/cfn/s3/cloudtrail_s3_data_logging_read_test.rego rename to rego/tests/rules/cfn/s3/cloudtrail_s3_data_logging_read_test.rego diff --git a/tests/rules/cfn/s3/cloudtrail_s3_data_logging_write_test.rego b/rego/tests/rules/cfn/s3/cloudtrail_s3_data_logging_write_test.rego similarity index 100% rename from tests/rules/cfn/s3/cloudtrail_s3_data_logging_write_test.rego rename to rego/tests/rules/cfn/s3/cloudtrail_s3_data_logging_write_test.rego diff --git a/tests/rules/cfn/s3/encryption_test.rego b/rego/tests/rules/cfn/s3/encryption_test.rego similarity index 100% rename from tests/rules/cfn/s3/encryption_test.rego rename to rego/tests/rules/cfn/s3/encryption_test.rego diff --git a/tests/rules/cfn/s3/https_access_test.rego b/rego/tests/rules/cfn/s3/https_access_test.rego similarity index 100% rename from tests/rules/cfn/s3/https_access_test.rego rename to rego/tests/rules/cfn/s3/https_access_test.rego diff --git a/tests/rules/cfn/s3/inputs/empty_template_infra.cfn b/rego/tests/rules/cfn/s3/inputs/empty_template_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/empty_template_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/empty_template_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/empty_template_infra.rego rename to rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego diff --git a/tests/rules/cfn/s3/inputs/invalid_missing_infra.cfn b/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_missing_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/invalid_missing_infra.rego rename to rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_block_public_access_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_encryption_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_encryption_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_encryption_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego diff --git a/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.cfn b/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.cfn similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.cfn rename to rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.cfn diff --git a/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego similarity index 100% rename from tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego rename to rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego diff --git a/tests/rules/cfn/vpc/default_security_group_test.rego b/rego/tests/rules/cfn/vpc/default_security_group_test.rego similarity index 100% rename from tests/rules/cfn/vpc/default_security_group_test.rego rename to rego/tests/rules/cfn/vpc/default_security_group_test.rego diff --git a/tests/rules/cfn/vpc/flow_logging_enabled_test.rego b/rego/tests/rules/cfn/vpc/flow_logging_enabled_test.rego similarity index 100% rename from tests/rules/cfn/vpc/flow_logging_enabled_test.rego rename to rego/tests/rules/cfn/vpc/flow_logging_enabled_test.rego diff --git a/tests/rules/cfn/vpc/ingress_22_test.rego b/rego/tests/rules/cfn/vpc/ingress_22_test.rego similarity index 100% rename from tests/rules/cfn/vpc/ingress_22_test.rego rename to rego/tests/rules/cfn/vpc/ingress_22_test.rego diff --git a/tests/rules/cfn/vpc/ingress_3389_test.rego b/rego/tests/rules/cfn/vpc/ingress_3389_test.rego similarity index 100% rename from tests/rules/cfn/vpc/ingress_3389_test.rego rename to rego/tests/rules/cfn/vpc/ingress_3389_test.rego diff --git a/tests/rules/cfn/vpc/inputs/default_security_group_infra.cfn b/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.cfn similarity index 100% rename from tests/rules/cfn/vpc/inputs/default_security_group_infra.cfn rename to rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.cfn diff --git a/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego b/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego similarity index 100% rename from tests/rules/cfn/vpc/inputs/default_security_group_infra.rego rename to rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego diff --git a/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.cfn b/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.cfn similarity index 100% rename from tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.cfn rename to rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.cfn diff --git a/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego b/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego similarity index 100% rename from tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego rename to rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego diff --git a/tests/rules/cfn/vpc/inputs/ingress_22_infra.cfn b/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.cfn similarity index 100% rename from tests/rules/cfn/vpc/inputs/ingress_22_infra.cfn rename to rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.cfn diff --git a/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego b/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego similarity index 100% rename from tests/rules/cfn/vpc/inputs/ingress_22_infra.rego rename to rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego diff --git a/tests/rules/cfn/vpc/inputs/ingress_3389_infra.cfn b/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.cfn similarity index 100% rename from tests/rules/cfn/vpc/inputs/ingress_3389_infra.cfn rename to rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.cfn diff --git a/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego b/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego similarity index 100% rename from tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego rename to rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego diff --git a/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.cfn b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.cfn similarity index 100% rename from tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.cfn rename to rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.cfn diff --git a/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego similarity index 100% rename from tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego rename to rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego diff --git a/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.cfn b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.cfn similarity index 100% rename from tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.cfn rename to rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.cfn diff --git a/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego similarity index 100% rename from tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego rename to rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego diff --git a/tests/rules/cfn/vpc/nacl_ingress_22_test.rego b/rego/tests/rules/cfn/vpc/nacl_ingress_22_test.rego similarity index 100% rename from tests/rules/cfn/vpc/nacl_ingress_22_test.rego rename to rego/tests/rules/cfn/vpc/nacl_ingress_22_test.rego diff --git a/tests/rules/cfn/vpc/nacl_ingress_3389_test.rego b/rego/tests/rules/cfn/vpc/nacl_ingress_3389_test.rego similarity index 100% rename from tests/rules/cfn/vpc/nacl_ingress_3389_test.rego rename to rego/tests/rules/cfn/vpc/nacl_ingress_3389_test.rego diff --git a/tests/rules/tf/aws/cloudfront/distribution_https_test.rego b/rego/tests/rules/tf/aws/cloudfront/distribution_https_test.rego similarity index 100% rename from tests/rules/tf/aws/cloudfront/distribution_https_test.rego rename to rego/tests/rules/tf/aws/cloudfront/distribution_https_test.rego diff --git a/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego similarity index 100% rename from tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego rename to rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego diff --git a/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tf b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tf similarity index 100% rename from tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tf rename to rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tf diff --git a/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego similarity index 100% rename from tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego rename to rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego diff --git a/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tf b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tf similarity index 100% rename from tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tf rename to rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tf diff --git a/tests/rules/tf/aws/cloudtrail/log_file_validation_test.rego b/rego/tests/rules/tf/aws/cloudtrail/log_file_validation_test.rego similarity index 100% rename from tests/rules/tf/aws/cloudtrail/log_file_validation_test.rego rename to rego/tests/rules/tf/aws/cloudtrail/log_file_validation_test.rego diff --git a/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego similarity index 100% rename from tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego rename to rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego diff --git a/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tf b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tf similarity index 100% rename from tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tf rename to rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tf diff --git a/tests/rules/tf/aws/ebs/volume_encrypted_test.rego b/rego/tests/rules/tf/aws/ebs/volume_encrypted_test.rego similarity index 100% rename from tests/rules/tf/aws/ebs/volume_encrypted_test.rego rename to rego/tests/rules/tf/aws/ebs/volume_encrypted_test.rego diff --git a/tests/rules/tf/aws/iam/admin_policy_test.rego b/rego/tests/rules/tf/aws/iam/admin_policy_test.rego similarity index 100% rename from tests/rules/tf/aws/iam/admin_policy_test.rego rename to rego/tests/rules/tf/aws/iam/admin_policy_test.rego diff --git a/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego similarity index 100% rename from tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego rename to rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego diff --git a/tests/rules/tf/aws/iam/inputs/admin_policy_infra.tf b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.tf similarity index 100% rename from tests/rules/tf/aws/iam/inputs/admin_policy_infra.tf rename to rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.tf diff --git a/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego similarity index 100% rename from tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego rename to rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego diff --git a/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tf b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tf similarity index 100% rename from tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tf rename to rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tf diff --git a/tests/rules/tf/aws/iam/user_attached_policy_test.rego b/rego/tests/rules/tf/aws/iam/user_attached_policy_test.rego similarity index 100% rename from tests/rules/tf/aws/iam/user_attached_policy_test.rego rename to rego/tests/rules/tf/aws/iam/user_attached_policy_test.rego diff --git a/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego similarity index 100% rename from tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego rename to rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego diff --git a/tests/rules/tf/aws/kms/inputs/key_rotation_infra.tf b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.tf similarity index 100% rename from tests/rules/tf/aws/kms/inputs/key_rotation_infra.tf rename to rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.tf diff --git a/tests/rules/tf/aws/kms/key_rotation_test.rego b/rego/tests/rules/tf/aws/kms/key_rotation_test.rego similarity index 100% rename from tests/rules/tf/aws/kms/key_rotation_test.rego rename to rego/tests/rules/tf/aws/kms/key_rotation_test.rego diff --git a/tests/rules/tf/aws/s3/bucket_sse_test.rego b/rego/tests/rules/tf/aws/s3/bucket_sse_test.rego similarity index 100% rename from tests/rules/tf/aws/s3/bucket_sse_test.rego rename to rego/tests/rules/tf/aws/s3/bucket_sse_test.rego diff --git a/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego similarity index 100% rename from tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego rename to rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego diff --git a/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tf b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tf similarity index 100% rename from tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tf rename to rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tf diff --git a/tests/rules/tf/aws/security_group/ingress_anywhere_rdp_test.rego b/rego/tests/rules/tf/aws/security_group/ingress_anywhere_rdp_test.rego similarity index 100% rename from tests/rules/tf/aws/security_group/ingress_anywhere_rdp_test.rego rename to rego/tests/rules/tf/aws/security_group/ingress_anywhere_rdp_test.rego diff --git a/tests/rules/tf/aws/security_group/ingress_anywhere_ssh_test.rego b/rego/tests/rules/tf/aws/security_group/ingress_anywhere_ssh_test.rego similarity index 100% rename from tests/rules/tf/aws/security_group/ingress_anywhere_ssh_test.rego rename to rego/tests/rules/tf/aws/security_group/ingress_anywhere_ssh_test.rego diff --git a/tests/rules/tf/aws/security_group/ingress_anywhere_test.rego b/rego/tests/rules/tf/aws/security_group/ingress_anywhere_test.rego similarity index 100% rename from tests/rules/tf/aws/security_group/ingress_anywhere_test.rego rename to rego/tests/rules/tf/aws/security_group/ingress_anywhere_test.rego diff --git a/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego similarity index 100% rename from tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego rename to rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego diff --git a/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tf b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tf similarity index 100% rename from tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tf rename to rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tf diff --git a/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego similarity index 100% rename from tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego rename to rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego diff --git a/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tf b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tf similarity index 100% rename from tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tf rename to rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tf diff --git a/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego similarity index 100% rename from tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego rename to rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego diff --git a/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tf b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tf similarity index 100% rename from tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tf rename to rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tf diff --git a/tests/rules/tf/aws/vpc/flow_log_test.rego b/rego/tests/rules/tf/aws/vpc/flow_log_test.rego similarity index 100% rename from tests/rules/tf/aws/vpc/flow_log_test.rego rename to rego/tests/rules/tf/aws/vpc/flow_log_test.rego diff --git a/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego similarity index 100% rename from tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego rename to rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego diff --git a/tests/rules/tf/aws/vpc/inputs/flow_log_infra.tf b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.tf similarity index 100% rename from tests/rules/tf/aws/vpc/inputs/flow_log_infra.tf rename to rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.tf diff --git a/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego similarity index 100% rename from tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego rename to rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego diff --git a/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tf b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tf similarity index 100% rename from tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tf rename to rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tf diff --git a/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego similarity index 100% rename from tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego rename to rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego diff --git a/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tf b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tf similarity index 100% rename from tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tf rename to rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tf diff --git a/tests/rules/tf/azurerm/network/security_group_no_inbound_22_test.rego b/rego/tests/rules/tf/azurerm/network/security_group_no_inbound_22_test.rego similarity index 100% rename from tests/rules/tf/azurerm/network/security_group_no_inbound_22_test.rego rename to rego/tests/rules/tf/azurerm/network/security_group_no_inbound_22_test.rego diff --git a/tests/rules/tf/azurerm/network/security_group_no_inbound_3389_test.rego b/rego/tests/rules/tf/azurerm/network/security_group_no_inbound_3389_test.rego similarity index 100% rename from tests/rules/tf/azurerm/network/security_group_no_inbound_3389_test.rego rename to rego/tests/rules/tf/azurerm/network/security_group_no_inbound_3389_test.rego diff --git a/tests/rules/tf/azurerm/sql/firewall_no_inbound_all_test.rego b/rego/tests/rules/tf/azurerm/sql/firewall_no_inbound_all_test.rego similarity index 100% rename from tests/rules/tf/azurerm/sql/firewall_no_inbound_all_test.rego rename to rego/tests/rules/tf/azurerm/sql/firewall_no_inbound_all_test.rego diff --git a/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego similarity index 100% rename from tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego rename to rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego diff --git a/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tf b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tf similarity index 100% rename from tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tf rename to rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tf diff --git a/tests/rules/tf/azurerm/storage/account_deny_access_test.rego b/rego/tests/rules/tf/azurerm/storage/account_deny_access_test.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/account_deny_access_test.rego rename to rego/tests/rules/tf/azurerm/storage/account_deny_access_test.rego diff --git a/tests/rules/tf/azurerm/storage/account_microsoft_services_test.rego b/rego/tests/rules/tf/azurerm/storage/account_microsoft_services_test.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/account_microsoft_services_test.rego rename to rego/tests/rules/tf/azurerm/storage/account_microsoft_services_test.rego diff --git a/tests/rules/tf/azurerm/storage/account_secure_transfer_test.rego b/rego/tests/rules/tf/azurerm/storage/account_secure_transfer_test.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/account_secure_transfer_test.rego rename to rego/tests/rules/tf/azurerm/storage/account_secure_transfer_test.rego diff --git a/tests/rules/tf/azurerm/storage/container_private_access_test.rego b/rego/tests/rules/tf/azurerm/storage/container_private_access_test.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/container_private_access_test.rego rename to rego/tests/rules/tf/azurerm/storage/container_private_access_test.rego diff --git a/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego rename to rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego diff --git a/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tf b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tf similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tf rename to rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tf diff --git a/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego rename to rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego diff --git a/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tf b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tf similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tf rename to rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tf diff --git a/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego rename to rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego diff --git a/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tf b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tf similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tf rename to rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tf diff --git a/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego rename to rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego diff --git a/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf similarity index 100% rename from tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf rename to rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf diff --git a/tests/rules/tf/google/compute/firewall_no_ingress_22_test.rego b/rego/tests/rules/tf/google/compute/firewall_no_ingress_22_test.rego similarity index 100% rename from tests/rules/tf/google/compute/firewall_no_ingress_22_test.rego rename to rego/tests/rules/tf/google/compute/firewall_no_ingress_22_test.rego diff --git a/tests/rules/tf/google/compute/firewall_no_ingress_3389_test.rego b/rego/tests/rules/tf/google/compute/firewall_no_ingress_3389_test.rego similarity index 100% rename from tests/rules/tf/google/compute/firewall_no_ingress_3389_test.rego rename to rego/tests/rules/tf/google/compute/firewall_no_ingress_3389_test.rego diff --git a/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego similarity index 100% rename from tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego rename to rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego diff --git a/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tf b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tf similarity index 100% rename from tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tf rename to rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tf diff --git a/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego similarity index 100% rename from tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego rename to rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego diff --git a/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tf b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tf similarity index 100% rename from tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tf rename to rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tf diff --git a/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego similarity index 100% rename from tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego rename to rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego diff --git a/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tf b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tf similarity index 100% rename from tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tf rename to rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tf diff --git a/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego similarity index 100% rename from tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego rename to rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego diff --git a/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tf b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tf similarity index 100% rename from tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tf rename to rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tf diff --git a/tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego b/rego/tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego similarity index 100% rename from tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego rename to rego/tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego diff --git a/tests/rules/tf/google/compute/subnet_private_google_access_test.rego b/rego/tests/rules/tf/google/compute/subnet_private_google_access_test.rego similarity index 100% rename from tests/rules/tf/google/compute/subnet_private_google_access_test.rego rename to rego/tests/rules/tf/google/compute/subnet_private_google_access_test.rego diff --git a/tests/rules/tf/google/kms/cryptokey_rotate_test.rego b/rego/tests/rules/tf/google/kms/cryptokey_rotate_test.rego similarity index 100% rename from tests/rules/tf/google/kms/cryptokey_rotate_test.rego rename to rego/tests/rules/tf/google/kms/cryptokey_rotate_test.rego diff --git a/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego similarity index 100% rename from tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego rename to rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego diff --git a/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tf b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tf similarity index 100% rename from tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tf rename to rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tf diff --git a/regula.png b/regula.png deleted file mode 100644 index 9488f3a6e2d7e4dd1ab8ce3c214e6a9a2ee0160b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 408362 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�&LZ?UGq~1E+wKQ93ab$6L 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
    |Zwqd@fEzLv2x$tKQ41$4&ftI{p~WHGGX{sN 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%0D;0|0|I>r`tr$r- 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

    ;t@-UwvT`l%T&pbO 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≪hOT93q| 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 Date: Sat, 24 Apr 2021 14:16:42 -0400 Subject: [PATCH 02/65] [RM-5352] Recurse dirs --- cmd/run.go | 38 +++++++--------- pkg/loader/base/base.go | 11 ++--- pkg/loader/cfn/loader.go | 48 ++++++++++++-------- pkg/loader/get-loader.go | 98 +++++++++++++++++++++++++++++++++++----- pkg/rego/runner.go | 3 +- pkg/reporter/base.go | 4 +- pkg/reporter/json.go | 4 +- 7 files changed, 139 insertions(+), 67 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index a0044940..675b4959 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -26,37 +26,31 @@ func NewRunCommand() *cobra.Command { os.Exit(1) } - for _, input := range args { - inputLoader, err := loader.GetLoaderByFileName(input) - if err != nil { - fmt.Println(err) - os.Exit(1) - } + loadedFiles, err := loader.LoadPaths(args) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + results, err := ruleRunner.Run(loadedFiles.RegulaInput()) + if err != nil { + fmt.Println(err) + os.Exit(1) + } - opaInput, err := inputLoader.OpaInput() + reporterFunc, _ := reporter.GetReporter("json") + for _, r := range results { + output, err := reporter.ParseRegulaOutput(r) if err != nil { fmt.Println(err) os.Exit(1) } - - results, err := ruleRunner.Run(opaInput) - + report, err := reporterFunc(loadedFiles, output) if err != nil { fmt.Println(err) os.Exit(1) } - - reporterFunc, _ := reporter.GetReporter("json") - - for _, r := range results { - output, err := reporter.ParseRegulaOutput(r) - report, err := reporterFunc(&inputLoader, output) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - fmt.Println(report) - } + fmt.Println(report) } }, } diff --git a/pkg/loader/base/base.go b/pkg/loader/base/base.go index 9261bee4..dff90261 100644 --- a/pkg/loader/base/base.go +++ b/pkg/loader/base/base.go @@ -14,19 +14,14 @@ var InputTypeIds = map[InputType][]string{ CfnYaml: {"cfn-yaml"}, } -type OpaInput map[string]interface{} - type Location struct { Line int Col int } +type RegulaInput map[string]interface{} + type Loader interface { - Load(filePath string) error - OpaInput() (OpaInput, error) + RegulaInput() RegulaInput Location(attributePath string) (*Location, error) } - -type Detector interface { - DetectLoader(filePath string) (Loader, error) -} diff --git a/pkg/loader/cfn/loader.go b/pkg/loader/cfn/loader.go index 98804ea9..d822eca3 100644 --- a/pkg/loader/cfn/loader.go +++ b/pkg/loader/cfn/loader.go @@ -10,34 +10,42 @@ import ( ) type CfnYamlLoader struct { + path string template cfnTemplate } -func NewCfnYamlLoader(filePath string) (*CfnYamlLoader, error) { - loader := &CfnYamlLoader{} - return loader, loader.Load(filePath) +func NewCfnYamlLoader(path string) (*CfnYamlLoader, error) { + // loader := + fileData, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("Failed to read file: %v", err) + } + template := &cfnTemplate{} + if err = yaml.Unmarshal(fileData, &template); err != nil { + return nil, fmt.Errorf("Failed to unmarshal CloudFormation YAML file: %v", err) + } + return &CfnYamlLoader{ + path: path, + template: *template, + }, nil + // loader.template = *template + // return nil + // return loader, loader.load(path) // if err := ; err != nil { // return nil, err // } // return loader, nil } -func (l *CfnYamlLoader) Load(filePath string) error { - fileData, err := ioutil.ReadFile(filePath) - if err != nil { - return fmt.Errorf("Failed to read file: %v", err) - } +// func (l *CfnYamlLoader) load(path string) error { - template := &cfnTemplate{} - if err = yaml.Unmarshal(fileData, &template); err != nil { - return fmt.Errorf("Failed to unmarshal CloudFormation YAML file: %v", err) - } - l.template = *template - return nil -} +// } -func (l *CfnYamlLoader) OpaInput() (base.OpaInput, error) { - return l.template.OpaInput, nil +func (l *CfnYamlLoader) RegulaInput() base.RegulaInput { + return base.RegulaInput{ + "filepath": l.path, + "content": l.template.Contents, + } } func (l *CfnYamlLoader) Location(_ string) (*base.Location, error) { @@ -45,15 +53,15 @@ func (l *CfnYamlLoader) Location(_ string) (*base.Location, error) { } type cfnTemplate struct { - OpaInput base.OpaInput + Contents map[string]interface{} } func (t *cfnTemplate) UnmarshalYAML(node *yaml.Node) error { - opaInput, err := decodeMap(node) + contents, err := decodeMap(node) if err != nil { return err } - t.OpaInput = opaInput + t.Contents = contents return nil } diff --git a/pkg/loader/get-loader.go b/pkg/loader/get-loader.go index d78382df..9e253e48 100644 --- a/pkg/loader/get-loader.go +++ b/pkg/loader/get-loader.go @@ -2,17 +2,83 @@ package loader import ( "fmt" + "io/fs" + "os" "path/filepath" "github.com/fugue/regula/pkg/loader/base" - "github.com/fugue/regula/pkg/loader/cfn" "github.com/fugue/regula/pkg/loader/yaml" ) -func GetLoaderByFileName(path string) (base.Loader, error) { - ext := filepath.Ext(path) +type LoadedFiles struct { + Loaders map[string]base.Loader +} + +func (l *LoadedFiles) RegulaInput() []base.RegulaInput { + input := []base.RegulaInput{} + for _, loader := range l.Loaders { + input = append(input, loader.RegulaInput()) + } + return input +} + +func LoadPaths(paths []string) (*LoadedFiles, error) { + loaders := map[string]base.Loader{} + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + ext := filepath.Ext(path) + if !recognizedExts[ext] { + return nil + } + loader, err := loadFile(path) + if err != nil { + // Want to ignore files we can't load when we're + // recursing through a directory. + return nil + } + loaders[path] = loader + return nil + } + for _, path := range paths { + info, err := os.Stat(path) + if err != nil { + return nil, err + } + if info.IsDir() { + err := filepath.WalkDir(path, walkDirFunc) + if err != nil { + return nil, err + } + continue + } + loader, err := loadFile(path) + if err != nil { + return nil, err + } + loaders[path] = loader + } + + if len(loaders) < 1 { + return nil, fmt.Errorf("No loadable files in provided paths: %v", paths) + } + + return &LoadedFiles{ + Loaders: loaders, + }, nil +} + +var recognizedExts map[string]bool = map[string]bool{ + ".yaml": true, + ".yml": true, +} - switch ext { +func loadFile(path string) (base.Loader, error) { + switch ext := filepath.Ext(path); ext { case ".yaml", ".yml": return yaml.DetectYamlLoader(path) default: @@ -20,11 +86,19 @@ func GetLoaderByFileName(path string) (base.Loader, error) { } } -func GetLoaderByInputType(path string, inputType base.InputType) (base.Loader, error) { - switch inputType { - case base.CfnYaml: - return cfn.NewCfnYamlLoader(path) - default: - return nil, fmt.Errorf("Unsupported input type: %s", base.InputTypeIds[inputType]) - } -} +// func LoadPathsByInputType(paths []string, inputType base.InputType) (LoadedFiles, error) { + +// } + +// func GetLoaderByFileName(path string) (base.Loader, error) { + +// } + +// func GetLoaderByInputType(path string, inputType base.InputType) (base.Loader, error) { +// switch inputType { +// case base.CfnYaml: +// return cfn.NewCfnYamlLoader(path) +// default: +// return nil, fmt.Errorf("Unsupported input type: %s", base.InputTypeIds[inputType]) +// } +// } diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go index 883ae6e4..57531d24 100644 --- a/pkg/rego/runner.go +++ b/pkg/rego/runner.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" + "github.com/fugue/regula/pkg/loader/base" "github.com/open-policy-agent/opa/rego" ) @@ -155,7 +156,7 @@ func NewRuleRunner(options *RuleRunnerOptions) (*RuleRunner, error) { } // Run evaluates rules against an input. -func (r *RuleRunner) Run(input map[string]interface{}) (rego.ResultSet, error) { +func (r *RuleRunner) Run(input []base.RegulaInput) (rego.ResultSet, error) { results, err := r.Query.Eval(r.Ctx, rego.EvalInput(input)) if err != nil { diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index 19c61ebc..0de38b15 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -3,7 +3,7 @@ package reporter import ( "encoding/json" - "github.com/fugue/regula/pkg/loader/base" + "github.com/fugue/regula/pkg/loader" "github.com/open-policy-agent/opa/rego" ) @@ -46,4 +46,4 @@ func ParseRegulaOutput(r rego.Result) (*RegulaOutput, error) { return output, nil } -type Reporter func(l *base.Loader, r *RegulaOutput) (string, error) +type Reporter func(l *loader.LoadedFiles, r *RegulaOutput) (string, error) diff --git a/pkg/reporter/json.go b/pkg/reporter/json.go index 52a8d172..66ec063a 100644 --- a/pkg/reporter/json.go +++ b/pkg/reporter/json.go @@ -3,10 +3,10 @@ package reporter import ( "encoding/json" - "github.com/fugue/regula/pkg/loader/base" + "github.com/fugue/regula/pkg/loader" ) -func JsonReporter(l *base.Loader, r *RegulaOutput) (string, error) { +func JsonReporter(l *loader.LoadedFiles, r *RegulaOutput) (string, error) { j, err := json.MarshalIndent(r, "", " ") if err != nil { return "", err From 2af6258f19f8b0321a3309204c5892e6e8b50112 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sat, 24 Apr 2021 14:58:13 -0400 Subject: [PATCH 03/65] [RM-5352] Ugly hack to make includes work --- cmd/run.go | 16 +++++++++++++++- pkg/rego/runner.go | 39 +++++++++++++++++++++++++++------------ 2 files changed, 42 insertions(+), 13 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 675b4959..158b1dd2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -16,9 +16,21 @@ func NewRunCommand() *cobra.Command { Use: "run", Short: "Run Regula rules on inputs", Run: func(cmd *cobra.Command, args []string) { + includes, err := cmd.Flags().GetStringSlice("include") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + userOnly, err := cmd.Flags().GetBool("user-only") + if err != nil { + fmt.Println(err) + os.Exit(1) + } ctx := context.TODO() ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ - Ctx: ctx, + Ctx: ctx, + UserOnly: userOnly, + Includes: includes, }) if err != nil { @@ -55,6 +67,8 @@ func NewRunCommand() *cobra.Command { }, } + cmd.Flags().StringSliceP("include", "i", nil, "Select rego libraries to include") + cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") return cmd } diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go index 57531d24..b144e9cc 100644 --- a/pkg/rego/runner.go +++ b/pkg/rego/runner.go @@ -5,6 +5,7 @@ import ( "embed" "fmt" "io/fs" + "io/ioutil" "os" "path/filepath" @@ -39,15 +40,30 @@ var LoadExts map[string]bool = map[string]bool{ ".json": true, } -func loadModule(fsys fs.FS, path string) (func(r *rego.Rego), error) { - contents, err := fs.ReadFile(fsys, path) +type reader func(path string) ([]byte, error) +type walker func(path string, walkDirFunc fs.WalkDirFunc) error + +func fsReader(fsys fs.FS) reader { + return func(path string) ([]byte, error) { + return fs.ReadFile(fsys, path) + } +} + +func fsWalker(fsys fs.FS) walker { + return func(path string, walkDirFunc fs.WalkDirFunc) error { + return fs.WalkDir(fsys, path, walkDirFunc) + } +} + +func loadModule(r reader, path string) (func(r *rego.Rego), error) { + contents, err := r(path) if err != nil { return nil, err } return rego.Module(path, string(contents)), nil } -func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { +func loadDirectory(r reader, w walker, path string) ([]func(r *rego.Rego), error) { modules := []func(r *rego.Rego){} walkDirFunc := func(path string, d fs.DirEntry, err error) error { if err != nil { @@ -59,7 +75,7 @@ func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { if ext := filepath.Ext(path); !LoadExts[ext] { return nil } - module, err := loadModule(fsys, path) + module, err := loadModule(r, path) if err != nil { return err } @@ -67,7 +83,7 @@ func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { return nil } - if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { + if err := w(path, walkDirFunc); err != nil { return nil, err } @@ -75,15 +91,14 @@ func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { } func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { - modules := make([]func(r *rego.Rego), len(includes)) - fsys := os.DirFS("") + modules := []func(r *rego.Rego){} for _, path := range includes { - info, err := fs.Stat(fsys, path) + info, err := os.Stat(path) if err != nil { return nil, err } if info.IsDir() { - dirModules, err := loadDirectory(fsys, path) + dirModules, err := loadDirectory(ioutil.ReadFile, filepath.WalkDir, path) if err != nil { return nil, err } @@ -94,7 +109,7 @@ func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { if ext := filepath.Ext(path); !LoadExts[ext] { return nil, fmt.Errorf("Unsupported file type %v in includes: %v", ext, path) } - module, err := loadModule(fsys, path) + module, err := loadModule(ioutil.ReadFile, path) if err != nil { return nil, err } @@ -107,13 +122,13 @@ func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { } func loadRegula(userOnly bool) ([]func(r *rego.Rego), error) { - modules, err := loadDirectory(regulaLib, "lib") + modules, err := loadDirectory(fsReader(regulaLib), fsWalker(regulaLib), "lib") if err != nil { return nil, err } if !userOnly { - rules, err := loadDirectory(regulaRules, "rules") + rules, err := loadDirectory(fsReader(regulaRules), fsWalker(regulaRules), "rules") if err != nil { return nil, err } From 16e44b13fae4780e51d4bd3d153d05f857f8a1bb Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sat, 24 Apr 2021 15:08:35 -0400 Subject: [PATCH 04/65] [RM-5352] Generate RegulaInput in order of filenames --- pkg/loader/get-loader.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/loader/get-loader.go b/pkg/loader/get-loader.go index 9e253e48..4190adf0 100644 --- a/pkg/loader/get-loader.go +++ b/pkg/loader/get-loader.go @@ -5,6 +5,7 @@ import ( "io/fs" "os" "path/filepath" + "sort" "github.com/fugue/regula/pkg/loader/base" "github.com/fugue/regula/pkg/loader/yaml" @@ -15,9 +16,14 @@ type LoadedFiles struct { } func (l *LoadedFiles) RegulaInput() []base.RegulaInput { + keys := []string{} + for k, _ := range l.Loaders { + keys = append(keys, k) + } + sort.Strings(keys) input := []base.RegulaInput{} - for _, loader := range l.Loaders { - input = append(input, loader.RegulaInput()) + for _, k := range keys { + input = append(input, l.Loaders[k].RegulaInput()) } return input } From 655319c4f2d9d832719cfcad4e226ecd610fa09d Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sat, 24 Apr 2021 15:24:32 -0400 Subject: [PATCH 05/65] [RM-5352] Remove problematic rwildcard from make --- Makefile | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index ab4c9790..455057c9 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,8 @@ BINARY = regula INSTALLED_BINARY = /usr/local/bin/$(BINARY) -CLI_SOURCE = $(call rwildcard,./pkg,*.go) -REGO_SOURCE = $(call rwildcard,./rego/lib,*.rego) $(call rwildcard,./rego/rules,*.rego) +CLI_SOURCE = $(shell find pkg -type f -name '*.go') +REGO_LIB_SOURCE = $(shell find rego/lib -type f -name '*.rego') +REGO_RULES_SOURCE = $(shell find rego/rules -type f -name '*.rego') GO = GO111MODULE=on go VERSION = $(shell cat VERSION) GITCOMMIT = $(shell git rev-parse --short HEAD 2> /dev/null || true) @@ -11,11 +12,19 @@ define LDFLAGS endef CLI_BUILD = $(GO) build -ldflags="$(LDFLAGS) -s -w" GOLINT = $(shell go env GOPATH)/bin/golint +COPIED_REGO_LIB = pkg/rego/lib +COPIED_REGO_RULES = pkg/rego/rules + +$(COPIED_REGO_LIB): $(REGO_LIB_SOURCE) + cp -R rego/lib $(COPIED_REGO_LIB) + +$(COPIED_REGO_RULES): $(REGO_RULES_SOURCE) + cp -R rego/rules $(COPIED_REGO_RULES) $(GOLINT): $(GO) get -u golang.org/x/lint/golint -$(BINARY): $(REGO_SOURCE) $(CLI_SOURCE) +$(BINARY): $(CLI_SOURCE) $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) $(CLI_BUILD) -v -o $@ $(BINARY)-linux-amd64: $(SOURCE) @@ -48,5 +57,6 @@ lint: $(GOLINT) ./... $(GO) vet ./... -# https://stackoverflow.com/questions/2483182/recursive-wildcards-in-gnu-make -rwildcard=$(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d)) +.PHONY: printsrc +printsrc: + @echo $(CLI_SOURCE) From cf2ffde886ea6bca961727fd2aca0c60fcd134c3 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sat, 24 Apr 2021 17:37:34 -0400 Subject: [PATCH 06/65] [RM-5352] Small osFs implementation --- go.mod | 1 - go.sum | 8 -------- pkg/rego/runner.go | 47 +++++++++++++++++++++------------------------- 3 files changed, 21 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index cb7ac1d6..72f4cfd9 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.16 require ( github.com/kr/text v0.2.0 // indirect - github.com/markbates/pkger v0.17.1 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 github.com/spf13/cobra v1.1.3 diff --git a/go.sum b/go.sum index fa8e99b7..f3a44e2f 100644 --- a/go.sum +++ b/go.sum @@ -63,7 +63,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -91,8 +90,6 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= -github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -195,8 +192,6 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= -github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -259,7 +254,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -326,7 +320,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -533,7 +526,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go index b144e9cc..612d551d 100644 --- a/pkg/rego/runner.go +++ b/pkg/rego/runner.go @@ -5,7 +5,6 @@ import ( "embed" "fmt" "io/fs" - "io/ioutil" "os" "path/filepath" @@ -40,30 +39,15 @@ var LoadExts map[string]bool = map[string]bool{ ".json": true, } -type reader func(path string) ([]byte, error) -type walker func(path string, walkDirFunc fs.WalkDirFunc) error - -func fsReader(fsys fs.FS) reader { - return func(path string) ([]byte, error) { - return fs.ReadFile(fsys, path) - } -} - -func fsWalker(fsys fs.FS) walker { - return func(path string, walkDirFunc fs.WalkDirFunc) error { - return fs.WalkDir(fsys, path, walkDirFunc) - } -} - -func loadModule(r reader, path string) (func(r *rego.Rego), error) { - contents, err := r(path) +func loadModule(fsys fs.FS, path string) (func(r *rego.Rego), error) { + contents, err := fs.ReadFile(fsys, path) if err != nil { return nil, err } return rego.Module(path, string(contents)), nil } -func loadDirectory(r reader, w walker, path string) ([]func(r *rego.Rego), error) { +func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { modules := []func(r *rego.Rego){} walkDirFunc := func(path string, d fs.DirEntry, err error) error { if err != nil { @@ -75,7 +59,7 @@ func loadDirectory(r reader, w walker, path string) ([]func(r *rego.Rego), error if ext := filepath.Ext(path); !LoadExts[ext] { return nil } - module, err := loadModule(r, path) + module, err := loadModule(fsys, path) if err != nil { return err } @@ -83,7 +67,7 @@ func loadDirectory(r reader, w walker, path string) ([]func(r *rego.Rego), error return nil } - if err := w(path, walkDirFunc); err != nil { + if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { return nil, err } @@ -92,13 +76,14 @@ func loadDirectory(r reader, w walker, path string) ([]func(r *rego.Rego), error func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { modules := []func(r *rego.Rego){} + o := osFs{} for _, path := range includes { - info, err := os.Stat(path) + info, err := fs.Stat(o, path) if err != nil { return nil, err } if info.IsDir() { - dirModules, err := loadDirectory(ioutil.ReadFile, filepath.WalkDir, path) + dirModules, err := loadDirectory(o, path) if err != nil { return nil, err } @@ -109,7 +94,7 @@ func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { if ext := filepath.Ext(path); !LoadExts[ext] { return nil, fmt.Errorf("Unsupported file type %v in includes: %v", ext, path) } - module, err := loadModule(ioutil.ReadFile, path) + module, err := loadModule(o, path) if err != nil { return nil, err } @@ -122,13 +107,13 @@ func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { } func loadRegula(userOnly bool) ([]func(r *rego.Rego), error) { - modules, err := loadDirectory(fsReader(regulaLib), fsWalker(regulaLib), "lib") + modules, err := loadDirectory(regulaLib, "lib") if err != nil { return nil, err } if !userOnly { - rules, err := loadDirectory(fsReader(regulaRules), fsWalker(regulaRules), "rules") + rules, err := loadDirectory(regulaRules, "rules") if err != nil { return nil, err } @@ -180,3 +165,13 @@ func (r *RuleRunner) Run(input []base.RegulaInput) (rego.ResultSet, error) { return results, nil } + +type osFs struct{} + +func (o osFs) Open(name string) (fs.File, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + return f, nil +} From c88aa3ecd35a041a1c8b276c7cb4198b6b68df7d Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sun, 25 Apr 2021 11:07:21 -0400 Subject: [PATCH 07/65] [RM-5352] Add install make target --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 455057c9..ec9bc5cd 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,9 @@ $(BINARY)-linux-amd64: $(SOURCE) $(BINARY)-darwin-amd64: $(SOURCE) GOOS=darwin GOARCH=amd64 $(CLI_BUILD) -o $@ +$(INSTALLED_BINARY): $(BINARY) + cp $(BINARY) $(INSTALLED_BINARY) + release: $(BINARY)-linux-amd64 $(BINARY)-darwin-amd64 .PHONY: install From 7c068be7cfcd1864f31e10f24dec59c00cb6d118 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sun, 25 Apr 2021 11:52:45 -0400 Subject: [PATCH 08/65] [RM-5352] Override input type detection --- cmd/run.go | 9 ++++++++- go.mod | 1 + go.sum | 26 ++++++++++++++++++++++++++ pkg/loader/base/base.go | 4 +++- pkg/loader/get-loader.go | 25 ++++++++++++++++++++++--- 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 158b1dd2..e7042dbd 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -6,12 +6,15 @@ import ( "os" "github.com/fugue/regula/pkg/loader" + "github.com/fugue/regula/pkg/loader/base" "github.com/fugue/regula/pkg/rego" "github.com/fugue/regula/pkg/reporter" "github.com/spf13/cobra" + "github.com/thediveo/enumflag" ) func NewRunCommand() *cobra.Command { + var inputType base.InputType cmd := &cobra.Command{ Use: "run", Short: "Run Regula rules on inputs", @@ -38,7 +41,7 @@ func NewRunCommand() *cobra.Command { os.Exit(1) } - loadedFiles, err := loader.LoadPaths(args) + loadedFiles, err := loader.LoadPaths(args, inputType) if err != nil { fmt.Println(err) os.Exit(1) @@ -69,6 +72,10 @@ func NewRunCommand() *cobra.Command { cmd.Flags().StringSliceP("include", "i", nil, "Select rego libraries to include") cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") + cmd.Flags().VarP( + enumflag.New(&inputType, "input-type", base.InputTypeIds, enumflag.EnumCaseInsensitive), + "input-type", "t", + "Explicitly set the input type") return cmd } diff --git a/go.mod b/go.mod index 72f4cfd9..235c36e5 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 github.com/spf13/cobra v1.1.3 + github.com/thediveo/enumflag v0.10.1 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) diff --git a/go.sum b/go.sum index f3a44e2f..0f85c551 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,7 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= @@ -50,6 +51,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -133,6 +135,7 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -162,6 +165,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= @@ -181,6 +185,7 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -191,6 +196,7 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -229,7 +235,12 @@ github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:v github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= +github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= +github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/open-policy-agent/opa v0.26.0 h1:FI0woFdGA73reU8OzSMzgHLFK+XeDMxKIlBpvvpRqDQ= github.com/open-policy-agent/opa v0.26.0/go.mod h1:iGThTRECCfKQKICueOZkXUi0opN7BR3qiAnIrNHCmlI= @@ -296,6 +307,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= @@ -305,6 +317,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= @@ -312,6 +325,7 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -322,14 +336,18 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/thediveo/enumflag v0.10.1 h1:DB3Ag69VZ7BCv6jzKECrZ0ebZrHLzFRMIFYt96s4OxM= +github.com/thediveo/enumflag v0.10.1/go.mod h1:KyVhQUPzreSw85oJi2uSjFM0ODLKXBH0rPod7zc2pmI= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/wasmerio/go-ext-wasm v0.3.1 h1:G95XP3fE2FszQSwIU+fHPBYzD0Csmd2ef33snQXNA5Q= github.com/wasmerio/go-ext-wasm v0.3.1/go.mod h1:VGyarTzasuS7k5KhSIGpM3tciSZlkP31Mp9VJTHMMeI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -392,12 +410,14 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200927032502-5d4f70055728 h1:5wtQIAulKU5AbLQOkjxl32UufnIOqgBX72pS0AV14H0= golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -428,14 +448,17 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -469,6 +492,7 @@ golang.org/x/tools v0.0.0-20201009032223-96877f285f7e/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -515,10 +539,12 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8X gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= diff --git a/pkg/loader/base/base.go b/pkg/loader/base/base.go index dff90261..d73a5fb3 100644 --- a/pkg/loader/base/base.go +++ b/pkg/loader/base/base.go @@ -3,12 +3,14 @@ package base type InputType int const ( - TfPlan InputType = iota + Auto InputType = iota + TfPlan CfnJson CfnYaml ) var InputTypeIds = map[InputType][]string{ + Auto: {"auto"}, TfPlan: {"tf-plan"}, CfnJson: {"cfn-json"}, CfnYaml: {"cfn-yaml"}, diff --git a/pkg/loader/get-loader.go b/pkg/loader/get-loader.go index 4190adf0..86b1350c 100644 --- a/pkg/loader/get-loader.go +++ b/pkg/loader/get-loader.go @@ -8,6 +8,7 @@ import ( "sort" "github.com/fugue/regula/pkg/loader/base" + "github.com/fugue/regula/pkg/loader/cfn" "github.com/fugue/regula/pkg/loader/yaml" ) @@ -28,8 +29,12 @@ func (l *LoadedFiles) RegulaInput() []base.RegulaInput { return input } -func LoadPaths(paths []string) (*LoadedFiles, error) { +func LoadPaths(paths []string, inputType base.InputType) (*LoadedFiles, error) { loaders := map[string]base.Loader{} + loaderFunc, err := getLoader(inputType) + if err != nil { + return nil, err + } walkDirFunc := func(path string, d fs.DirEntry, err error) error { if err != nil { return err @@ -41,7 +46,7 @@ func LoadPaths(paths []string) (*LoadedFiles, error) { if !recognizedExts[ext] { return nil } - loader, err := loadFile(path) + loader, err := loaderFunc(path) if err != nil { // Want to ignore files we can't load when we're // recursing through a directory. @@ -62,7 +67,7 @@ func LoadPaths(paths []string) (*LoadedFiles, error) { } continue } - loader, err := loadFile(path) + loader, err := loaderFunc(path) if err != nil { return nil, err } @@ -92,6 +97,20 @@ func loadFile(path string) (base.Loader, error) { } } +func getLoader(inputType base.InputType) (func(path string) (base.Loader, error), error) { + if inputType == base.Auto { + return loadFile, nil + } + switch inputType { + case base.CfnYaml: + return func(path string) (base.Loader, error) { + return cfn.NewCfnYamlLoader(path) + }, nil + default: + return nil, fmt.Errorf("Unsupported input type %v", base.InputTypeIds[inputType]) + } +} + // func LoadPathsByInputType(paths []string, inputType base.InputType) (LoadedFiles, error) { // } From f5f3b36d466e4b92fd36f417e4cf801d00b80bc3 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sun, 25 Apr 2021 12:37:59 -0400 Subject: [PATCH 09/65] [RM-5352] Implement severity and non-zero exit code --- cmd/run.go | 31 ++++++++++++++++----------- pkg/reporter/base.go | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index e7042dbd..1b8ddffa 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -15,6 +15,7 @@ import ( func NewRunCommand() *cobra.Command { var inputType base.InputType + severity := reporter.Unknown cmd := &cobra.Command{ Use: "run", Short: "Run Regula rules on inputs", @@ -54,18 +55,20 @@ func NewRunCommand() *cobra.Command { } reporterFunc, _ := reporter.GetReporter("json") - for _, r := range results { - output, err := reporter.ParseRegulaOutput(r) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - report, err := reporterFunc(loadedFiles, output) - if err != nil { - fmt.Println(err) - os.Exit(1) - } - fmt.Println(report) + r := results[0] + output, err := reporter.ParseRegulaOutput(r) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + report, err := reporterFunc(loadedFiles, output) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + fmt.Println(report) + if output.ExceedsSeverity(severity) { + os.Exit(1) } }, } @@ -76,6 +79,10 @@ func NewRunCommand() *cobra.Command { enumflag.New(&inputType, "input-type", base.InputTypeIds, enumflag.EnumCaseInsensitive), "input-type", "t", "Explicitly set the input type") + cmd.Flags().VarP( + enumflag.New(&severity, "severity", reporter.SeverityIds, enumflag.EnumCaseInsensitive), + "severity", "s", + "Set the minimum severity that will result in a non-zero exit code.") return cmd } diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index 0de38b15..3cb776c9 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -7,11 +7,62 @@ import ( "github.com/open-policy-agent/opa/rego" ) +type Severity int + +const ( + Unknown Severity = iota + Informational + Low + Medium + High + Critical + Off +) + +var SeverityIds = map[Severity][]string{ + Unknown: {"unknown"}, + Informational: {"informational"}, + Low: {"low"}, + Medium: {"medium"}, + High: {"high"}, + Critical: {"critical"}, + Off: {"off"}, +} + type RegulaOutput struct { RuleResults []RuleResult `json:"rule_results"` Summary Summary `json:"summary"` } +var regulaSeverities map[string]Severity = map[string]Severity{ + "Unknown": Unknown, + "Informational": Informational, + "Low": Low, + "Medium": Medium, + "High": High, + "Critical": Critical, +} + +func (o RegulaOutput) ExceedsSeverity(severity Severity) bool { + maxSeverity := Unknown + hasFailures := false + for s, count := range o.Summary.Severities { + if count < 1 { + continue + } + level, ok := regulaSeverities[s] + if !ok { + continue + } + hasFailures = true + if level > maxSeverity { + maxSeverity = level + } + } + + return hasFailures && maxSeverity >= severity +} + type RuleResult struct { Controls []string `json:"controls"` Filepath string `json:"filepath"` From 22eb0cb85b07051fe0673dd4db74981bca2bd787 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sun, 25 Apr 2021 12:42:04 -0400 Subject: [PATCH 10/65] [RM-5352] Use cwd as default --- Makefile | 2 +- cmd/run.go | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ec9bc5cd..acdac01f 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BINARY = regula INSTALLED_BINARY = /usr/local/bin/$(BINARY) -CLI_SOURCE = $(shell find pkg -type f -name '*.go') +CLI_SOURCE = $(shell find cmd pkg -type f -name '*.go') REGO_LIB_SOURCE = $(shell find rego/lib -type f -name '*.rego') REGO_RULES_SOURCE = $(shell find rego/rules -type f -name '*.rego') GO = GO111MODULE=on go diff --git a/cmd/run.go b/cmd/run.go index 1b8ddffa..9d34656e 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -19,7 +19,7 @@ func NewRunCommand() *cobra.Command { cmd := &cobra.Command{ Use: "run", Short: "Run Regula rules on inputs", - Run: func(cmd *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, paths []string) { includes, err := cmd.Flags().GetStringSlice("include") if err != nil { fmt.Println(err) @@ -42,7 +42,16 @@ func NewRunCommand() *cobra.Command { os.Exit(1) } - loadedFiles, err := loader.LoadPaths(args, inputType) + if len(paths) < 1 { + wd, err := os.Getwd() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + paths = []string{wd} + } + + loadedFiles, err := loader.LoadPaths(paths, inputType) if err != nil { fmt.Println(err) os.Exit(1) From 63e8b0f8a8bd4b5c716fa39e282eb505a9accb83 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Sun, 25 Apr 2021 12:46:07 -0400 Subject: [PATCH 11/65] [RM-5352] Skip severity check if no failures --- pkg/reporter/base.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index 3cb776c9..14a4ed5c 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -44,8 +44,10 @@ var regulaSeverities map[string]Severity = map[string]Severity{ } func (o RegulaOutput) ExceedsSeverity(severity Severity) bool { + if o.Summary.RuleResults["FAIL"] < 1 { + return false + } maxSeverity := Unknown - hasFailures := false for s, count := range o.Summary.Severities { if count < 1 { continue @@ -54,13 +56,12 @@ func (o RegulaOutput) ExceedsSeverity(severity Severity) bool { if !ok { continue } - hasFailures = true if level > maxSeverity { maxSeverity = level } } - return hasFailures && maxSeverity >= severity + return maxSeverity >= severity } type RuleResult struct { From b49548679f9edc5900816a9066aff9e1aa609312 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 26 Apr 2021 08:56:59 -0400 Subject: [PATCH 12/65] [RM-5352] Load input from stdin --- cmd/run.go | 24 +++++++++++---- go.mod | 1 + go.sum | 1 + pkg/loader/cfn/loader.go | 24 ++++----------- pkg/loader/get-loader.go | 61 ++++++++++++++++++++++++--------------- pkg/loader/yaml/detect.go | 8 ++--- pkg/reporter/json.go | 10 +++++-- 7 files changed, 76 insertions(+), 53 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 9d34656e..ecdcdc3a 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -11,6 +11,7 @@ import ( "github.com/fugue/regula/pkg/reporter" "github.com/spf13/cobra" "github.com/thediveo/enumflag" + "golang.org/x/crypto/ssh/terminal" ) func NewRunCommand() *cobra.Command { @@ -43,12 +44,25 @@ func NewRunCommand() *cobra.Command { } if len(paths) < 1 { - wd, err := os.Getwd() - if err != nil { - fmt.Println(err) - os.Exit(1) + if terminal.IsTerminal(int(os.Stdin.Fd())) { + wd, err := os.Getwd() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + paths = []string{wd} + } else { + paths = []string{"-"} + } + } + + if inputType == base.Auto { + for _, p := range paths { + if p == "-" { + fmt.Println("Automatic type detection not supported for stdin. Please specify an input type with: -t ") + os.Exit(1) + } } - paths = []string{wd} } loadedFiles, err := loader.LoadPaths(paths, inputType) diff --git a/go.mod b/go.mod index 235c36e5..58972408 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/open-policy-agent/opa v0.26.0 github.com/spf13/cobra v1.1.3 github.com/thediveo/enumflag v0.10.1 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) diff --git a/go.sum b/go.sum index 0f85c551..8ff5910b 100644 --- a/go.sum +++ b/go.sum @@ -374,6 +374,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= diff --git a/pkg/loader/cfn/loader.go b/pkg/loader/cfn/loader.go index d822eca3..fdc86e1e 100644 --- a/pkg/loader/cfn/loader.go +++ b/pkg/loader/cfn/loader.go @@ -2,7 +2,6 @@ package cfn import ( "fmt" - "io/ioutil" "strings" "github.com/fugue/regula/pkg/loader/base" @@ -14,33 +13,22 @@ type CfnYamlLoader struct { template cfnTemplate } -func NewCfnYamlLoader(path string) (*CfnYamlLoader, error) { +func NewCfnYamlLoader(path string, contents []byte) (*CfnYamlLoader, error) { // loader := - fileData, err := ioutil.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("Failed to read file: %v", err) - } + // fileData, err := ioutil.ReadFile(path) + // if err != nil { + // return nil, fmt.Errorf("Failed to read file: %v", err) + // } template := &cfnTemplate{} - if err = yaml.Unmarshal(fileData, &template); err != nil { + if err := yaml.Unmarshal(contents, &template); err != nil { return nil, fmt.Errorf("Failed to unmarshal CloudFormation YAML file: %v", err) } return &CfnYamlLoader{ path: path, template: *template, }, nil - // loader.template = *template - // return nil - // return loader, loader.load(path) - // if err := ; err != nil { - // return nil, err - // } - // return loader, nil } -// func (l *CfnYamlLoader) load(path string) error { - -// } - func (l *CfnYamlLoader) RegulaInput() base.RegulaInput { return base.RegulaInput{ "filepath": l.path, diff --git a/pkg/loader/get-loader.go b/pkg/loader/get-loader.go index 86b1350c..640257ed 100644 --- a/pkg/loader/get-loader.go +++ b/pkg/loader/get-loader.go @@ -3,6 +3,7 @@ package loader import ( "fmt" "io/fs" + "io/ioutil" "os" "path/filepath" "sort" @@ -46,7 +47,13 @@ func LoadPaths(paths []string, inputType base.InputType) (*LoadedFiles, error) { if !recognizedExts[ext] { return nil } - loader, err := loaderFunc(path) + contents, err := ioutil.ReadFile(path) + if err != nil { + // Want to ignore files we can't load when we're + // recursing through a directory. + return nil + } + loader, err := loaderFunc(path, contents) if err != nil { // Want to ignore files we can't load when we're // recursing through a directory. @@ -56,6 +63,14 @@ func LoadPaths(paths []string, inputType base.InputType) (*LoadedFiles, error) { return nil } for _, path := range paths { + if path == "-" { + loader, err := loadStdin(inputType) + if err != nil { + return nil, err + } + loaders[path] = loader + continue + } info, err := os.Stat(path) if err != nil { return nil, err @@ -67,7 +82,11 @@ func LoadPaths(paths []string, inputType base.InputType) (*LoadedFiles, error) { } continue } - loader, err := loaderFunc(path) + contents, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + loader, err := loaderFunc(path, contents) if err != nil { return nil, err } @@ -88,42 +107,38 @@ var recognizedExts map[string]bool = map[string]bool{ ".yml": true, } -func loadFile(path string) (base.Loader, error) { +func loadFile(path string, contents []byte) (base.Loader, error) { switch ext := filepath.Ext(path); ext { case ".yaml", ".yml": - return yaml.DetectYamlLoader(path) + return yaml.DetectYamlLoader(path, contents) default: return nil, fmt.Errorf("Unable to detect file type for file: %s", path) } } -func getLoader(inputType base.InputType) (func(path string) (base.Loader, error), error) { +func getLoader(inputType base.InputType) (func(path string, contents []byte) (base.Loader, error), error) { if inputType == base.Auto { return loadFile, nil } switch inputType { case base.CfnYaml: - return func(path string) (base.Loader, error) { - return cfn.NewCfnYamlLoader(path) + return func(path string, contents []byte) (base.Loader, error) { + return cfn.NewCfnYamlLoader(path, contents) }, nil default: return nil, fmt.Errorf("Unsupported input type %v", base.InputTypeIds[inputType]) } } -// func LoadPathsByInputType(paths []string, inputType base.InputType) (LoadedFiles, error) { - -// } - -// func GetLoaderByFileName(path string) (base.Loader, error) { - -// } - -// func GetLoaderByInputType(path string, inputType base.InputType) (base.Loader, error) { -// switch inputType { -// case base.CfnYaml: -// return cfn.NewCfnYamlLoader(path) -// default: -// return nil, fmt.Errorf("Unsupported input type: %s", base.InputTypeIds[inputType]) -// } -// } +func loadStdin(inputType base.InputType) (base.Loader, error) { + contents, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + switch inputType { + case base.CfnYaml: + return cfn.NewCfnYamlLoader("", contents) + default: + return nil, fmt.Errorf("Unsupported input type %v", base.InputTypeIds[inputType]) + } +} diff --git a/pkg/loader/yaml/detect.go b/pkg/loader/yaml/detect.go index d5ac787d..e67927dc 100644 --- a/pkg/loader/yaml/detect.go +++ b/pkg/loader/yaml/detect.go @@ -14,8 +14,8 @@ type yamlDetector struct { AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` } -func DetectYamlLoader(filePath string) (base.Loader, error) { - f, err := os.Open(filePath) +func DetectYamlLoader(path string, contents []byte) (base.Loader, error) { + f, err := os.Open(path) if err != nil { return nil, err } @@ -24,8 +24,8 @@ func DetectYamlLoader(filePath string) (base.Loader, error) { return nil, err } if d.AWSTemplateFormatVersion != "" { - return cfn.NewCfnYamlLoader(filePath) + return cfn.NewCfnYamlLoader(path, contents) } - return nil, fmt.Errorf("Unknown input type in file %s", filePath) + return nil, fmt.Errorf("Unknown input type in file %s", path) } diff --git a/pkg/reporter/json.go b/pkg/reporter/json.go index 66ec063a..1c9ca10e 100644 --- a/pkg/reporter/json.go +++ b/pkg/reporter/json.go @@ -1,15 +1,19 @@ package reporter import ( + "bytes" "encoding/json" "github.com/fugue/regula/pkg/loader" ) func JsonReporter(l *loader.LoadedFiles, r *RegulaOutput) (string, error) { - j, err := json.MarshalIndent(r, "", " ") - if err != nil { + buf := &bytes.Buffer{} + enc := json.NewEncoder(buf) + enc.SetEscapeHTML(false) + enc.SetIndent("", " ") + if err := enc.Encode(r); err != nil { return "", err } - return string(j), nil + return buf.String(), nil } From 1859041f7f96c217866c545b1bfc85fc6f6d4970 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 26 Apr 2021 16:52:38 -0400 Subject: [PATCH 13/65] [RM-5352] pkg/loader improvements --- cmd/run.go | 19 +-- pkg/loader/{base => }/base.go | 15 ++- pkg/loader/{cfn/loader.go => cfnyaml.go} | 30 ++--- pkg/loader/get-loader.go | 144 ---------------------- pkg/loader/json.go | 42 +++++++ pkg/loader/json/detect.go | 1 - pkg/loader/loadpaths.go | 147 +++++++++++++++++++++++ pkg/loader/yaml.go | 23 ++++ pkg/loader/yaml/detect.go | 31 ----- pkg/rego/runner.go | 13 +- 10 files changed, 242 insertions(+), 223 deletions(-) rename pkg/loader/{base => }/base.go (76%) rename pkg/loader/{cfn/loader.go => cfnyaml.go} (87%) delete mode 100644 pkg/loader/get-loader.go create mode 100644 pkg/loader/json.go delete mode 100644 pkg/loader/json/detect.go create mode 100644 pkg/loader/loadpaths.go create mode 100644 pkg/loader/yaml.go delete mode 100644 pkg/loader/yaml/detect.go diff --git a/cmd/run.go b/cmd/run.go index ecdcdc3a..c3311323 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -6,7 +6,6 @@ import ( "os" "github.com/fugue/regula/pkg/loader" - "github.com/fugue/regula/pkg/loader/base" "github.com/fugue/regula/pkg/rego" "github.com/fugue/regula/pkg/reporter" "github.com/spf13/cobra" @@ -15,7 +14,7 @@ import ( ) func NewRunCommand() *cobra.Command { - var inputType base.InputType + var inputType loader.InputType severity := reporter.Unknown cmd := &cobra.Command{ Use: "run", @@ -56,16 +55,10 @@ func NewRunCommand() *cobra.Command { } } - if inputType == base.Auto { - for _, p := range paths { - if p == "-" { - fmt.Println("Automatic type detection not supported for stdin. Please specify an input type with: -t ") - os.Exit(1) - } - } - } - - loadedFiles, err := loader.LoadPaths(paths, inputType) + loadedFiles, err := loader.LoadPaths(loader.LoadPathsOptions{ + Paths: paths, + InputType: inputType, + }) if err != nil { fmt.Println(err) os.Exit(1) @@ -99,7 +92,7 @@ func NewRunCommand() *cobra.Command { cmd.Flags().StringSliceP("include", "i", nil, "Select rego libraries to include") cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") cmd.Flags().VarP( - enumflag.New(&inputType, "input-type", base.InputTypeIds, enumflag.EnumCaseInsensitive), + enumflag.New(&inputType, "input-type", loader.InputTypeIds, enumflag.EnumCaseInsensitive), "input-type", "t", "Explicitly set the input type") cmd.Flags().VarP( diff --git a/pkg/loader/base/base.go b/pkg/loader/base.go similarity index 76% rename from pkg/loader/base/base.go rename to pkg/loader/base.go index d73a5fb3..7355551a 100644 --- a/pkg/loader/base/base.go +++ b/pkg/loader/base.go @@ -1,4 +1,4 @@ -package base +package loader type InputType int @@ -16,14 +16,19 @@ var InputTypeIds = map[InputType][]string{ CfnYaml: {"cfn-yaml"}, } +type RegulaInput map[string]interface{} + +type Loader interface { + RegulaInput() RegulaInput +} + type Location struct { Line int Col int } -type RegulaInput map[string]interface{} - -type Loader interface { - RegulaInput() RegulaInput +type LocationAwareLoader interface { Location(attributePath string) (*Location, error) } + +type LoaderFactory func(path string, contents []byte) (Loader, error) diff --git a/pkg/loader/cfn/loader.go b/pkg/loader/cfnyaml.go similarity index 87% rename from pkg/loader/cfn/loader.go rename to pkg/loader/cfnyaml.go index fdc86e1e..05c702f2 100644 --- a/pkg/loader/cfn/loader.go +++ b/pkg/loader/cfnyaml.go @@ -1,45 +1,35 @@ -package cfn +package loader import ( "fmt" "strings" - "github.com/fugue/regula/pkg/loader/base" "gopkg.in/yaml.v3" ) -type CfnYamlLoader struct { - path string - template cfnTemplate -} - -func NewCfnYamlLoader(path string, contents []byte) (*CfnYamlLoader, error) { - // loader := - // fileData, err := ioutil.ReadFile(path) - // if err != nil { - // return nil, fmt.Errorf("Failed to read file: %v", err) - // } +func CfnYamlLoaderFactory(path string, contents []byte) (Loader, error) { template := &cfnTemplate{} if err := yaml.Unmarshal(contents, &template); err != nil { return nil, fmt.Errorf("Failed to unmarshal CloudFormation YAML file: %v", err) } - return &CfnYamlLoader{ + return &cfnYamlLoader{ path: path, template: *template, }, nil } -func (l *CfnYamlLoader) RegulaInput() base.RegulaInput { - return base.RegulaInput{ +type cfnYamlLoader struct { + path string + template cfnTemplate +} + +func (l *cfnYamlLoader) RegulaInput() RegulaInput { + return RegulaInput{ "filepath": l.path, "content": l.template.Contents, } } -func (l *CfnYamlLoader) Location(_ string) (*base.Location, error) { - return nil, fmt.Errorf("Location not implemented for this type") -} - type cfnTemplate struct { Contents map[string]interface{} } diff --git a/pkg/loader/get-loader.go b/pkg/loader/get-loader.go deleted file mode 100644 index 640257ed..00000000 --- a/pkg/loader/get-loader.go +++ /dev/null @@ -1,144 +0,0 @@ -package loader - -import ( - "fmt" - "io/fs" - "io/ioutil" - "os" - "path/filepath" - "sort" - - "github.com/fugue/regula/pkg/loader/base" - "github.com/fugue/regula/pkg/loader/cfn" - "github.com/fugue/regula/pkg/loader/yaml" -) - -type LoadedFiles struct { - Loaders map[string]base.Loader -} - -func (l *LoadedFiles) RegulaInput() []base.RegulaInput { - keys := []string{} - for k, _ := range l.Loaders { - keys = append(keys, k) - } - sort.Strings(keys) - input := []base.RegulaInput{} - for _, k := range keys { - input = append(input, l.Loaders[k].RegulaInput()) - } - return input -} - -func LoadPaths(paths []string, inputType base.InputType) (*LoadedFiles, error) { - loaders := map[string]base.Loader{} - loaderFunc, err := getLoader(inputType) - if err != nil { - return nil, err - } - walkDirFunc := func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - ext := filepath.Ext(path) - if !recognizedExts[ext] { - return nil - } - contents, err := ioutil.ReadFile(path) - if err != nil { - // Want to ignore files we can't load when we're - // recursing through a directory. - return nil - } - loader, err := loaderFunc(path, contents) - if err != nil { - // Want to ignore files we can't load when we're - // recursing through a directory. - return nil - } - loaders[path] = loader - return nil - } - for _, path := range paths { - if path == "-" { - loader, err := loadStdin(inputType) - if err != nil { - return nil, err - } - loaders[path] = loader - continue - } - info, err := os.Stat(path) - if err != nil { - return nil, err - } - if info.IsDir() { - err := filepath.WalkDir(path, walkDirFunc) - if err != nil { - return nil, err - } - continue - } - contents, err := ioutil.ReadFile(path) - if err != nil { - return nil, err - } - loader, err := loaderFunc(path, contents) - if err != nil { - return nil, err - } - loaders[path] = loader - } - - if len(loaders) < 1 { - return nil, fmt.Errorf("No loadable files in provided paths: %v", paths) - } - - return &LoadedFiles{ - Loaders: loaders, - }, nil -} - -var recognizedExts map[string]bool = map[string]bool{ - ".yaml": true, - ".yml": true, -} - -func loadFile(path string, contents []byte) (base.Loader, error) { - switch ext := filepath.Ext(path); ext { - case ".yaml", ".yml": - return yaml.DetectYamlLoader(path, contents) - default: - return nil, fmt.Errorf("Unable to detect file type for file: %s", path) - } -} - -func getLoader(inputType base.InputType) (func(path string, contents []byte) (base.Loader, error), error) { - if inputType == base.Auto { - return loadFile, nil - } - switch inputType { - case base.CfnYaml: - return func(path string, contents []byte) (base.Loader, error) { - return cfn.NewCfnYamlLoader(path, contents) - }, nil - default: - return nil, fmt.Errorf("Unsupported input type %v", base.InputTypeIds[inputType]) - } -} - -func loadStdin(inputType base.InputType) (base.Loader, error) { - contents, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return nil, err - } - switch inputType { - case base.CfnYaml: - return cfn.NewCfnYamlLoader("", contents) - default: - return nil, fmt.Errorf("Unsupported input type %v", base.InputTypeIds[inputType]) - } -} diff --git a/pkg/loader/json.go b/pkg/loader/json.go new file mode 100644 index 00000000..0acff969 --- /dev/null +++ b/pkg/loader/json.go @@ -0,0 +1,42 @@ +package loader + +import ( + "encoding/json" + "fmt" +) + +func JsonLoaderFactory(path string, contents []byte) (Loader, error) { + d := &jsonDetector{} + if err := json.Unmarshal(contents, d); err != nil { + return nil, fmt.Errorf("Unable to parse %v as json: %v", path, err) + } + if d.AWSTemplateFormatVersion == "" && d.TerraformVersion == "" { + return nil, fmt.Errorf("Unrecognized json file: %v", path) + } + + c := &map[string]interface{}{} + if err := json.Unmarshal(contents, c); err != nil { + return nil, fmt.Errorf("Unable to parse %v as recognized JSON type: %v", path, err) + } + return &jsonLoader{ + path: path, + content: c, + }, nil +} + +type jsonDetector struct { + AWSTemplateFormatVersion string `json:"AWSTemplateFormatVersion"` + TerraformVersion string `json:"terraform_version"` +} + +type jsonLoader struct { + path string + content *map[string]interface{} +} + +func (l *jsonLoader) RegulaInput() RegulaInput { + return RegulaInput{ + "filepath": l.path, + "content": l.content, + } +} diff --git a/pkg/loader/json/detect.go b/pkg/loader/json/detect.go deleted file mode 100644 index a5b981cc..00000000 --- a/pkg/loader/json/detect.go +++ /dev/null @@ -1 +0,0 @@ -package json diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go new file mode 100644 index 00000000..22035481 --- /dev/null +++ b/pkg/loader/loadpaths.go @@ -0,0 +1,147 @@ +package loader + +import ( + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "sort" +) + +type LoadedFiles struct { + Loaders map[string]Loader +} + +func (l *LoadedFiles) RegulaInput() []RegulaInput { + keys := []string{} + for k := range l.Loaders { + keys = append(keys, k) + } + sort.Strings(keys) + input := []RegulaInput{} + for _, k := range keys { + input = append(input, l.Loaders[k].RegulaInput()) + } + return input +} + +type LoadPathsOptions struct { + Paths []string + InputType InputType +} + +func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { + loaders := map[string]Loader{} + loaderFactory, err := loaderFactoryByInputType(options.InputType) + if err != nil { + return nil, err + } + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); !recognizedExts[ext] { + return nil + } + contents, err := os.ReadFile(path) + if err != nil { + // Ignore files we can't read + return nil + } + loader, err := loaderFactory(path, contents) + if err != nil { + // Ignore files we can't load + return nil + } + loaders[path] = loader + return nil + } + for _, path := range options.Paths { + if path == "-" { + contents, err := io.ReadAll(os.Stdin) + if err != nil { + return nil, err + } + loader, err := loaderFactory("", contents) + if err != nil { + return nil, err + } + loaders[path] = loader + continue + } + info, err := os.Stat(path) + if err != nil { + return nil, err + } + if info.IsDir() { + err := filepath.WalkDir(path, walkDirFunc) + if err != nil { + return nil, err + } + continue + } + contents, err := os.ReadFile(path) + if err != nil { + return nil, err + } + loader, err := loaderFactory(path, contents) + if err != nil { + return nil, err + } + loaders[path] = loader + } + + if len(loaders) < 1 { + return nil, fmt.Errorf("No loadable files in provided paths: %v", options.Paths) + } + + return &LoadedFiles{ + Loaders: loaders, + }, nil +} + +var recognizedExts map[string]bool = map[string]bool{ + ".yaml": true, + ".yml": true, + ".json": true, +} + +func loaderFactoryByInputType(inputType InputType) (LoaderFactory, error) { + switch inputType { + case Auto: + return autoLoaderFactory, nil + case CfnYaml: + return CfnYamlLoaderFactory, nil + case CfnJson, TfPlan: + return JsonLoaderFactory, nil + default: + return nil, fmt.Errorf("Unsupported input type: %v", inputType) + } +} + +func autoLoaderFactory(path string, contents []byte) (Loader, error) { + if path == "" { + l, err := JsonLoaderFactory(path, contents) + if err == nil { + return l, nil + } + l, err = YamlLoaderFactory(path, contents) + if err == nil { + return l, nil + } + return nil, fmt.Errorf("Unable to detect input type of data from stdin.") + } + + switch ext := filepath.Ext(path); ext { + case ".yaml", ".yml": + return YamlLoaderFactory(path, contents) + case ".json": + return JsonLoaderFactory(path, contents) + default: + return nil, fmt.Errorf("Unable to detect file type for file: %s", path) + } +} diff --git a/pkg/loader/yaml.go b/pkg/loader/yaml.go new file mode 100644 index 00000000..f697bfa0 --- /dev/null +++ b/pkg/loader/yaml.go @@ -0,0 +1,23 @@ +package loader + +import ( + "fmt" + + "gopkg.in/yaml.v3" +) + +func YamlLoaderFactory(path string, contents []byte) (Loader, error) { + d := &yamlDetector{} + if err := yaml.Unmarshal(contents, d); err != nil { + return nil, fmt.Errorf("Unable to parse %v as yaml: %v", path, err) + } + if d.AWSTemplateFormatVersion != "" { + return CfnYamlLoaderFactory(path, contents) + } + + return nil, fmt.Errorf("Unrecognized yaml file: %v", path) +} + +type yamlDetector struct { + AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` +} diff --git a/pkg/loader/yaml/detect.go b/pkg/loader/yaml/detect.go deleted file mode 100644 index e67927dc..00000000 --- a/pkg/loader/yaml/detect.go +++ /dev/null @@ -1,31 +0,0 @@ -package yaml - -import ( - "fmt" - "os" - - yaml "gopkg.in/yaml.v3" - - "github.com/fugue/regula/pkg/loader/base" - "github.com/fugue/regula/pkg/loader/cfn" -) - -type yamlDetector struct { - AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` -} - -func DetectYamlLoader(path string, contents []byte) (base.Loader, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - d := &yamlDetector{} - if err := yaml.NewDecoder(f).Decode(d); err != nil { - return nil, err - } - if d.AWSTemplateFormatVersion != "" { - return cfn.NewCfnYamlLoader(path, contents) - } - - return nil, fmt.Errorf("Unknown input type in file %s", path) -} diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go index 612d551d..f535c4a9 100644 --- a/pkg/rego/runner.go +++ b/pkg/rego/runner.go @@ -8,7 +8,7 @@ import ( "os" "path/filepath" - "github.com/fugue/regula/pkg/loader/base" + "github.com/fugue/regula/pkg/loader" "github.com/open-policy-agent/opa/rego" ) @@ -134,13 +134,8 @@ func NewRuleRunner(options *RuleRunnerOptions) (*RuleRunner, error) { if err != nil { return nil, fmt.Errorf("Failed to load includes: %v", err) } - regoFuncs := append( - regula, - append( - includes, - rego.Query("data.fugue.regula.report"), - )..., - ) + regoFuncs := append(regula, includes...) + regoFuncs = append(regoFuncs, rego.Query("data.fugue.regula.report")) query, err := rego.New( regoFuncs..., ).PrepareForEval(options.Ctx) @@ -156,7 +151,7 @@ func NewRuleRunner(options *RuleRunnerOptions) (*RuleRunner, error) { } // Run evaluates rules against an input. -func (r *RuleRunner) Run(input []base.RegulaInput) (rego.ResultSet, error) { +func (r *RuleRunner) Run(input []loader.RegulaInput) (rego.ResultSet, error) { results, err := r.Query.Eval(r.Ctx, rego.EvalInput(input)) if err != nil { From 093c6b50743fc4414d9dc3c0985f846d820b5329 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 26 Apr 2021 17:29:19 -0400 Subject: [PATCH 14/65] [RM-5352] Add table output format --- cmd/run.go | 7 +- go.mod | 2 + go.sum | 9 +++ pkg/reporter/base.go | 18 +++++ pkg/reporter/get-reporter.go | 17 ++-- pkg/reporter/json.go | 2 +- pkg/reporter/table.go | 147 +++++++++++++++++++++++++++++++++++ 7 files changed, 191 insertions(+), 11 deletions(-) create mode 100644 pkg/reporter/table.go diff --git a/cmd/run.go b/cmd/run.go index c3311323..a7f1853b 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -15,6 +15,7 @@ import ( func NewRunCommand() *cobra.Command { var inputType loader.InputType + var format reporter.Format severity := reporter.Unknown cmd := &cobra.Command{ Use: "run", @@ -70,7 +71,7 @@ func NewRunCommand() *cobra.Command { os.Exit(1) } - reporterFunc, _ := reporter.GetReporter("json") + reporterFunc, _ := reporter.GetReporter(format) r := results[0] output, err := reporter.ParseRegulaOutput(r) if err != nil { @@ -99,6 +100,10 @@ func NewRunCommand() *cobra.Command { enumflag.New(&severity, "severity", reporter.SeverityIds, enumflag.EnumCaseInsensitive), "severity", "s", "Set the minimum severity that will result in a non-zero exit code.") + cmd.Flags().VarP( + enumflag.New(&format, "format", reporter.FormatIds, enumflag.EnumCaseInsensitive), + "format", "f", + "Set the output format") return cmd } diff --git a/go.mod b/go.mod index 58972408..6742c350 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/fugue/regula go 1.16 require ( + github.com/alexeyco/simpletable v1.0.0 + github.com/fatih/color v1.7.0 github.com/kr/text v0.2.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 diff --git a/go.sum b/go.sum index 8ff5910b..ee427dd2 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alexeyco/simpletable v1.0.0 h1:ZQ+LvJ4bmoeHb+dclF64d0LX+7QAi7awsfCrptZrpHk= +github.com/alexeyco/simpletable v1.0.0/go.mod h1:VJWVTtGUnW7EKbMRH8cE13SigKGx/1fO2SeeOiGeBkk= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -76,6 +78,7 @@ github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaB github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= @@ -198,11 +201,15 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -297,6 +304,8 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index 14a4ed5c..c9a9e6e5 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -29,6 +29,24 @@ var SeverityIds = map[Severity][]string{ Off: {"off"}, } +type Format int + +const ( + Json Format = iota + Table + Junit + Tap + Tap13 +) + +var FormatIds = map[Format][]string{ + Json: {"json"}, + Table: {"table"}, + Junit: {"junit"}, + Tap: {"tap"}, + Tap13: {"tap13"}, +} + type RegulaOutput struct { RuleResults []RuleResult `json:"rule_results"` Summary Summary `json:"summary"` diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/get-reporter.go index 885bb95c..447cb554 100644 --- a/pkg/reporter/get-reporter.go +++ b/pkg/reporter/get-reporter.go @@ -2,14 +2,13 @@ package reporter import "fmt" -var Reporters map[string]Reporter = map[string]Reporter{ - "json": JsonReporter, -} - -func GetReporter(name string) (Reporter, error) { - reporter, ok := Reporters[name] - if ok { - return reporter, nil +func GetReporter(format Format) (Reporter, error) { + switch format { + case Json: + return JsonReporter, nil + case Table: + return TableReporter, nil + default: + return nil, fmt.Errorf("Unsupported or unrecognized reporter: %v", FormatIds[format]) } - return nil, fmt.Errorf("Unrecognized reporter: %v", name) } diff --git a/pkg/reporter/json.go b/pkg/reporter/json.go index 1c9ca10e..b9e92acb 100644 --- a/pkg/reporter/json.go +++ b/pkg/reporter/json.go @@ -7,7 +7,7 @@ import ( "github.com/fugue/regula/pkg/loader" ) -func JsonReporter(l *loader.LoadedFiles, r *RegulaOutput) (string, error) { +func JsonReporter(_ *loader.LoadedFiles, r *RegulaOutput) (string, error) { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetEscapeHTML(false) diff --git a/pkg/reporter/table.go b/pkg/reporter/table.go new file mode 100644 index 00000000..899ab1e5 --- /dev/null +++ b/pkg/reporter/table.go @@ -0,0 +1,147 @@ +package reporter + +import ( + "sort" + + "github.com/alexeyco/simpletable" + "github.com/fatih/color" + "github.com/fugue/regula/pkg/loader" +) + +func TableReporter(l *loader.LoadedFiles, o *RegulaOutput) (string, error) { + tableData := []TableRow{} + var overall string + if o.Summary.RuleResults["FAIL"] > 0 { + overall = "FAIL" + } else { + overall = "PASS" + } + for _, r := range o.RuleResults { + message := r.RuleMessage + if message == "" { + message = r.RuleSummary + } + tableRow := TableRow{ + Resource: r.ResourceId, + Type: r.ResourceType, + Filepath: r.Filepath, + Severity: colorizeSeverity(r), + RuleID: r.RuleId, + RuleName: r.RuleName, + Message: message, + Result: colorizeResult(r.RuleResult), + } + tableData = append(tableData, tableRow) + } + + sort.SliceStable(tableData, func(i, j int) bool { + if tableData[i].Filepath == tableData[j].Filepath { + if tableData[i].Resource == tableData[j].Resource { + return tableData[i].RuleID < tableData[j].RuleID + } + return tableData[i].Resource < tableData[j].Resource + } + return tableData[i].Filepath < tableData[j].Filepath + }) + + table := simpletable.New() + table.Header = &simpletable.Header{ + Cells: []*simpletable.Cell{ + {Align: simpletable.AlignCenter, Text: "Resource"}, + {Align: simpletable.AlignCenter, Text: "Type"}, + {Align: simpletable.AlignCenter, Text: "Filepath"}, + {Align: simpletable.AlignCenter, Text: "Severity"}, + {Align: simpletable.AlignCenter, Text: "Rule ID"}, + {Align: simpletable.AlignCenter, Text: "Rule Name"}, + {Align: simpletable.AlignCenter, Text: "Message"}, + {Align: simpletable.AlignCenter, Text: "Result"}, + }, + } + for _, row := range tableData { + table.Body.Cells = append(table.Body.Cells, row.toCell()) + } + table.Footer = &simpletable.Footer{ + Cells: []*simpletable.Cell{ + {}, + {}, + {}, + {}, + {}, + {}, + {Align: simpletable.AlignRight, Text: "Overall"}, + {Align: simpletable.AlignRight, Text: colorizeResult(overall)}, + }, + } + + table.SetStyle(simpletable.StyleDefault) + return table.String(), nil +} + +type TableRow struct { + Resource string + Type string + Filepath string + Severity string + RuleID string + RuleName string + Message string + Result string +} + +func (r TableRow) toCell() []*simpletable.Cell { + return []*simpletable.Cell{ + {Text: r.Resource}, + {Text: r.Type}, + {Text: r.Filepath}, + {Text: r.Severity}, + {Text: r.RuleID}, + {Text: r.RuleName}, + {Text: r.Message}, + {Text: r.Result}, + } +} + +var waivedColor func(...interface{}) string = color.New(color.FgBlack).SprintFunc() +var failedColor func(...interface{}) string = color.New(color.FgRed).SprintFunc() +var passedColor func(...interface{}) string = color.New(color.FgGreen).SprintFunc() +var unknownColor func(...interface{}) string = color.New(color.FgMagenta).SprintFunc() +var lowColor func(...interface{}) string = color.New(color.FgBlue).SprintFunc() +var mediumColor func(...interface{}) string = color.New(color.FgYellow).SprintFunc() +var highColor func(...interface{}) string = color.New(color.FgRed).SprintFunc() +var criticalColor func(...interface{}) string = color.New(color.BgRed, color.FgBlack).SprintFunc() + +func colorizeResult(result string) string { + switch result { + case "PASS": + return passedColor(result) + case "FAIL": + return failedColor(result) + case "WAIVED": + return waivedColor(result) + default: + return result + } +} + +func colorizeSeverity(r RuleResult) string { + if r.RuleResult == "PASS" { + return r.RuleSeverity + } + + if r.RuleResult == "WAIVED" { + return waivedColor(r.RuleSeverity) + } + + switch r.RuleSeverity { + case "Low": + return lowColor(r.RuleSeverity) + case "Medium": + return mediumColor(r.RuleSeverity) + case "High": + return highColor(r.RuleSeverity) + case "Critical": + return criticalColor(r.RuleSeverity) + default: + return r.RuleSeverity + } +} From ab1bc9353fa310331483e0a3fe2242281ffd666d Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 26 Apr 2021 20:26:20 -0400 Subject: [PATCH 15/65] [RM-5352] Add JUnit reporter --- pkg/reporter/base.go | 10 +++ pkg/reporter/get-reporter.go | 2 + pkg/reporter/junit.go | 154 +++++++++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 pkg/reporter/junit.go diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index c9a9e6e5..a38da2bb 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -98,6 +98,16 @@ type RuleResult struct { RuleSummary string `json:"rule_summary"` } +func (r RuleResult) Message() string { + if r.RuleMessage != "" { + return r.RuleMessage + } + if r.RuleSummary != "" { + return r.RuleSummary + } + return r.RuleDescription +} + type Summary struct { Filepaths []string `json:"filepaths"` RuleResults map[string]int `json:"rule_results"` diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/get-reporter.go index 447cb554..e1a54410 100644 --- a/pkg/reporter/get-reporter.go +++ b/pkg/reporter/get-reporter.go @@ -8,6 +8,8 @@ func GetReporter(format Format) (Reporter, error) { return JsonReporter, nil case Table: return TableReporter, nil + case Junit: + return JUnitReporter, nil default: return nil, fmt.Errorf("Unsupported or unrecognized reporter: %v", FormatIds[format]) } diff --git a/pkg/reporter/junit.go b/pkg/reporter/junit.go new file mode 100644 index 00000000..29c1bcf8 --- /dev/null +++ b/pkg/reporter/junit.go @@ -0,0 +1,154 @@ +package reporter + +import ( + "encoding/xml" + "fmt" + "sort" + + "github.com/fugue/regula/pkg/loader" +) + +func JUnitReporter(l *loader.LoadedFiles, o *RegulaOutput) (string, error) { + byFilepath := map[string]map[string]ResourceResults{} + for _, r := range o.RuleResults { + fileResults, ok := byFilepath[r.Filepath] + if !ok { + fileResults = map[string]ResourceResults{} + } + resourceResults, ok := fileResults[r.ResourceId] + if !ok { + resourceResults = ResourceResults{ + Results: []RuleResult{}, + ResourceId: r.ResourceId, + ResourceType: r.ResourceType, + } + } + resourceResults.Results = append(resourceResults.Results, r) + fileResults[r.ResourceId] = resourceResults + byFilepath[r.Filepath] = fileResults + } + filePaths := []string{} + for k := range byFilepath { + filePaths = append(filePaths, k) + } + sort.Strings(filePaths) + testSuites := []TestSuite{} + for _, f := range filePaths { + resourceIds := []string{} + for k := range byFilepath[f] { + resourceIds = append(resourceIds, k) + } + sort.Strings(resourceIds) + testCases := []TestCase{} + for _, r := range resourceIds { + resourceResults := byFilepath[f][r] + sort.SliceStable(resourceResults.Results, func(i, j int) bool { + return resourceResults.Results[i].RuleName < resourceResults.Results[j].RuleName + }) + skips := []TestSkipMessage{} + failures := []TestFailure{} + for _, result := range resourceResults.Results { + if result.RuleResult == "WAIVED" { + skips = append(skips, TestSkipMessage{ + Message: result.Message(), + }) + } else if result.RuleResult == "FAIL" { + failures = append(failures, TestFailure{ + Message: result.Message(), + Type: result.RuleName, + Contents: ruleMessage(result), + }) + } + } + testCase := TestCase{ + Name: r, + ClassName: resourceResults.ResourceType, + Assertions: len(resourceResults.Results), + } + if len(skips) > 0 { + testCase.SkipMessage = &skips + } + if len(failures) > 0 { + testCase.Failures = &failures + } + testCases = append(testCases, testCase) + } + testSuites = append(testSuites, TestSuite{ + Name: f, + Tests: len(testCases), + TestCases: testCases, + }) + } + + rootNode := TestSuites{ + Name: "Regula", + TestSuites: testSuites, + } + + x, err := xml.MarshalIndent(rootNode, "", " ") + if err != nil { + return "", err + } + return string(x), nil +} + +func ruleMessage(r RuleResult) string { + return fmt.Sprintf( + "Rule ID: %v\nRule Name: %v\nSeverity: %v\nMessage: %v", + r.RuleId, + r.RuleName, + r.RuleSeverity, + r.Message(), + ) +} + +func sortedKeys(m map[string]interface{}) []string { + keys := []string{} + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +type ResultsByFilepath struct { + Filepaths map[string]map[string]ResourceResults +} + +type ResourceResults struct { + Results []RuleResult + ResourceId string + ResourceType string +} + +type TestSuites struct { + XMLName xml.Name `xml:"testsuites"` + Name string `xml:"name,attr"` + TestSuites []TestSuite `xml:"testsuite"` +} + +type TestSuite struct { + XMLName xml.Name `xml:"testsuite"` + Name string `xml:"name,attr"` + Tests int `xml:"tests,attr"` + TestCases []TestCase `xml:"testcase"` +} + +type TestCase struct { + XMLName xml.Name `xml:"testcase"` + Name string `xml:"name,attr"` + ClassName string `xml:"classname,attr"` + Assertions int `xml:"assertions,attr"` + SkipMessage *[]TestSkipMessage `xml:"skipped,omitempty"` + Failures *[]TestFailure `xml:"error,omitempty"` +} + +type TestFailure struct { + Message string `xml:"message,attr"` + Type string `xml:"type,attr"` + Contents string `xml:",chardata"` +} + +type TestSkipMessage struct { + Message string `xml:"message,attr"` +} From d5d21fbdd691e2e353efa8594b78147f1219e71b Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 27 Apr 2021 13:59:21 -0400 Subject: [PATCH 16/65] [RM-5352] Add directory loading concept --- cmd/run.go | 4 +- pkg/loader/base.go | 141 +++++++++++++++++++++++++++++++++++++++- pkg/loader/cfnyaml.go | 37 ++++++++++- pkg/loader/json.go | 93 ++++++++++++++++++++++---- pkg/loader/loadpaths.go | 96 +++++++++++++-------------- pkg/loader/yaml.go | 44 ++++++++----- pkg/reporter/base.go | 4 +- pkg/reporter/json.go | 4 +- pkg/reporter/junit.go | 4 +- pkg/reporter/table.go | 3 +- 10 files changed, 337 insertions(+), 93 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index a7f1853b..bf02ef16 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -73,12 +73,12 @@ func NewRunCommand() *cobra.Command { reporterFunc, _ := reporter.GetReporter(format) r := results[0] - output, err := reporter.ParseRegulaOutput(r) + output, err := reporter.ParseRegulaOutput(loadedFiles, r) if err != nil { fmt.Println(err) os.Exit(1) } - report, err := reporterFunc(loadedFiles, output) + report, err := reporterFunc(output) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 7355551a..7b1660ce 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -1,5 +1,11 @@ package loader +import ( + "io" + "os" + "path/filepath" +) + type InputType int const ( @@ -23,6 +29,7 @@ type Loader interface { } type Location struct { + Path string Line int Col int } @@ -31,4 +38,136 @@ type LocationAwareLoader interface { Location(attributePath string) (*Location, error) } -type LoaderFactory func(path string, contents []byte) (Loader, error) +type LoaderFactory func(inputPath InputPath) (Loader, error) + +type TypeDetector struct { + DetectDirectory func(i InputDirectory) (Loader, error) + DetectFile func(i InputFile) (Loader, error) +} + +func NewTypeDetector(t *TypeDetector) *TypeDetector { + if t.DetectDirectory == nil { + t.DetectDirectory = func(i InputDirectory) (Loader, error) { + return nil, nil + } + } + if t.DetectFile == nil { + t.DetectFile = func(i InputFile) (Loader, error) { + return nil, nil + } + } + return t +} + +type InputPath interface { + DetectType(d TypeDetector) (Loader, error) + Children() []InputPath + IsDir() bool + Walk(w func(i InputPath) error) error + GetPath() string +} +type InputDirectory struct { + Path string + Name string + Contents []InputPath +} + +func (i InputDirectory) DetectType(d TypeDetector) (Loader, error) { + return d.DetectDirectory(i) +} + +func (i InputDirectory) Children() []InputPath { + return i.Contents +} + +func (i InputDirectory) IsDir() bool { + return true +} + +func (i InputDirectory) Walk(w func(i InputPath) error) error { + for _, c := range i.Contents { + if err := w(c); err != nil { + return err + } + if err := c.Walk(w); err != nil { + return err + } + } + return nil +} + +func (i InputDirectory) GetPath() string { + return i.Path +} + +func NewInputDirectory(path string, name string) (*InputDirectory, error) { + // name := filepath.Base(path) + contents := []InputPath{} + entries, err := os.ReadDir(path) + if err != nil { + return nil, err + } + for _, e := range entries { + n := e.Name() + p := filepath.Join(path, n) + var i InputPath + if e.IsDir() { + i, err = NewInputDirectory(p, n) + if err != nil { + return nil, err + } + + } else { + i = NewInputFile(p, n) + } + contents = append(contents, i) + } + return &InputDirectory{ + Path: path, + Name: name, + Contents: contents, + }, nil +} + +type InputFile struct { + Path string + Name string + Ext string +} + +func (i InputFile) DetectType(d TypeDetector) (Loader, error) { + return d.DetectFile(i) +} + +func (i InputFile) Children() []InputPath { + return nil +} + +func (i InputFile) IsDir() bool { + return false +} + +func (i InputFile) Walk(w func(i InputPath) error) error { + return nil +} + +func (i InputFile) GetPath() string { + return i.Path +} + +func (i InputFile) ReadContents() ([]byte, error) { + if i.Name == "-" { + return io.ReadAll(os.Stdin) + } + + return os.ReadFile(i.Path) +} + +func NewInputFile(path string, name string) *InputFile { + ext := filepath.Ext(path) + return &InputFile{ + Path: path, + Name: name, + Ext: ext, + } +} diff --git a/pkg/loader/cfnyaml.go b/pkg/loader/cfnyaml.go index 05c702f2..4c135528 100644 --- a/pkg/loader/cfnyaml.go +++ b/pkg/loader/cfnyaml.go @@ -7,7 +7,42 @@ import ( "gopkg.in/yaml.v3" ) -func CfnYamlLoaderFactory(path string, contents []byte) (Loader, error) { +var CfnYamlDetector = *NewTypeDetector(&TypeDetector{ + DetectFile: func(i InputFile) (Loader, error) { + contents, err := i.ReadContents() + if err != nil { + return nil, err + } + c := &struct { + AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` + }{} + if err := yaml.Unmarshal(contents, c); err != nil { + return nil, fmt.Errorf("Failed to parse YAML file %v: %v", i.Path, err) + } + if c.AWSTemplateFormatVersion != "" { + return baseCfnYamlLoaderFactory(i.Path, contents) + } + + return nil, fmt.Errorf("Input file is not CloudFormation: %v", i.Path) + }, +}) + +func CfnYamlLoaderFactory(i InputPath) (Loader, error) { + if i.IsDir() { + return nil, nil + } + f, ok := i.(InputFile) + if !ok { + return nil, fmt.Errorf("Unable to cast input as file: %v", i.GetPath()) + } + contents, err := f.ReadContents() + if err != nil { + return nil, err + } + return baseCfnYamlLoaderFactory(f.Path, contents) +} + +func baseCfnYamlLoaderFactory(path string, contents []byte) (Loader, error) { template := &cfnTemplate{} if err := yaml.Unmarshal(contents, &template); err != nil { return nil, fmt.Errorf("Failed to unmarshal CloudFormation YAML file: %v", err) diff --git a/pkg/loader/json.go b/pkg/loader/json.go index 0acff969..e342f6f8 100644 --- a/pkg/loader/json.go +++ b/pkg/loader/json.go @@ -5,15 +5,91 @@ import ( "fmt" ) -func JsonLoaderFactory(path string, contents []byte) (Loader, error) { - d := &jsonDetector{} - if err := json.Unmarshal(contents, d); err != nil { - return nil, fmt.Errorf("Unable to parse %v as json: %v", path, err) +var JsonTypeDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i InputFile) (Loader, error) { + if i.Ext != ".json" { + return nil, nil + } + contents, err := i.ReadContents() + if err != nil { + return nil, err + } + j := &struct { + AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` + TerraformVersion string `json:"terraform_version"` + }{} + if err := json.Unmarshal(contents, j); err != nil { + return nil, err + } + if j.AWSTemplateFormatVersion == "" && j.TerraformVersion == "" { + return nil, nil + } + return baseJsonLoaderFactory(i.Path, contents) + }, +}) + +var TfPlanDetector = *NewTypeDetector(&TypeDetector{ + DetectFile: func(i InputFile) (Loader, error) { + contents, err := i.ReadContents() + if err != nil { + return nil, err + } + return baseTfPlanDetector(i.Path, contents) + }, +}) + +func baseTfPlanDetector(path string, contents []byte) (Loader, error) { + j := &struct { + TerraformVersion string `json:"terraform_version"` + }{} + if err := json.Unmarshal(contents, j); err != nil { + return nil, err + } + if j.TerraformVersion == "" { + return nil, nil + } + return baseJsonLoaderFactory(path, contents) +} + +var CfnJsonDetector = *NewTypeDetector(&TypeDetector{ + DetectFile: func(i InputFile) (Loader, error) { + contents, err := i.ReadContents() + if err != nil { + return nil, err + } + return baseCfnJsonDetector(i.Path, contents) + }, +}) + +func baseCfnJsonDetector(path string, contents []byte) (Loader, error) { + j := &struct { + AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` + }{} + if err := json.Unmarshal(contents, j); err != nil { + return nil, err } - if d.AWSTemplateFormatVersion == "" && d.TerraformVersion == "" { - return nil, fmt.Errorf("Unrecognized json file: %v", path) + if j.AWSTemplateFormatVersion == "" { + return nil, nil } + return baseJsonLoaderFactory(path, contents) +} +func JsonLoaderFactory(i InputPath) (Loader, error) { + if i.IsDir() { + return nil, nil + } + f, ok := i.(InputFile) + if !ok { + return nil, fmt.Errorf("Unable to cast input as file: %v", i.GetPath()) + } + contents, err := f.ReadContents() + if err != nil { + return nil, err + } + return baseJsonLoaderFactory(f.Path, contents) +} + +func baseJsonLoaderFactory(path string, contents []byte) (Loader, error) { c := &map[string]interface{}{} if err := json.Unmarshal(contents, c); err != nil { return nil, fmt.Errorf("Unable to parse %v as recognized JSON type: %v", path, err) @@ -24,11 +100,6 @@ func JsonLoaderFactory(path string, contents []byte) (Loader, error) { }, nil } -type jsonDetector struct { - AWSTemplateFormatVersion string `json:"AWSTemplateFormatVersion"` - TerraformVersion string `json:"terraform_version"` -} - type jsonLoader struct { path string content *map[string]interface{} diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 22035481..288e4a05 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -2,8 +2,6 @@ package loader import ( "fmt" - "io" - "io/fs" "os" "path/filepath" "sort" @@ -37,64 +35,54 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { if err != nil { return nil, err } - walkDirFunc := func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if ext := filepath.Ext(path); !recognizedExts[ext] { - return nil - } - contents, err := os.ReadFile(path) - if err != nil { - // Ignore files we can't read - return nil - } - loader, err := loaderFactory(path, contents) - if err != nil { - // Ignore files we can't load - return nil + walkFunc := func(i InputPath) error { + // Ignore errors when we're recursing + loader, _ := loaderFactory(i) + if loader != nil { + loaders[i.GetPath()] = loader } - loaders[path] = loader return nil } for _, path := range options.Paths { if path == "-" { - contents, err := io.ReadAll(os.Stdin) + loader, err := loaderFactory(InputFile{ + Path: "", + Name: "-", + Ext: "", + }) if err != nil { return nil, err } - loader, err := loaderFactory("", contents) - if err != nil { - return nil, err + if loader != nil { + loaders[path] = loader } - loaders[path] = loader continue } + name := filepath.Base(path) info, err := os.Stat(path) if err != nil { return nil, err } + var i InputPath if info.IsDir() { - err := filepath.WalkDir(path, walkDirFunc) + i, err = NewInputDirectory(path, name) if err != nil { return nil, err } - continue + } else { + i = NewInputFile(path, name) } - contents, err := os.ReadFile(path) + loader, err := loaderFactory(i) if err != nil { return nil, err } - loader, err := loaderFactory(path, contents) - if err != nil { + if loader != nil { + loaders[path] = loader + } + if err := i.Walk(walkFunc); err != nil { return nil, err } - loaders[path] = loader } - if len(loaders) < 1 { return nil, fmt.Errorf("No loadable files in provided paths: %v", options.Paths) } @@ -104,12 +92,6 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { }, nil } -var recognizedExts map[string]bool = map[string]bool{ - ".yaml": true, - ".yml": true, - ".json": true, -} - func loaderFactoryByInputType(inputType InputType) (LoaderFactory, error) { switch inputType { case Auto: @@ -123,25 +105,37 @@ func loaderFactoryByInputType(inputType InputType) (LoaderFactory, error) { } } -func autoLoaderFactory(path string, contents []byte) (Loader, error) { - if path == "" { - l, err := JsonLoaderFactory(path, contents) +func autoLoaderFactory(i InputPath) (Loader, error) { + if i.IsDir() { + return nil, nil + } + + if i.GetPath() == "" { + l, err := JsonLoaderFactory(i) if err == nil { return l, nil } - l, err = YamlLoaderFactory(path, contents) + l, err = CfnYamlLoaderFactory(i) if err == nil { return l, nil } return nil, fmt.Errorf("Unable to detect input type of data from stdin.") } - switch ext := filepath.Ext(path); ext { - case ".yaml", ".yml": - return YamlLoaderFactory(path, contents) - case ".json": - return JsonLoaderFactory(path, contents) - default: - return nil, fmt.Errorf("Unable to detect file type for file: %s", path) + l, err := i.DetectType(*YamlTypeDetector) + if err != nil { + return nil, err + } + if l != nil { + return l, nil } + l, err = i.DetectType(*JsonTypeDetector) + if err != nil { + return nil, err + } + if l != nil { + return l, nil + } + + return nil, fmt.Errorf("Unable to detect file type for file: %s", i.GetPath()) } diff --git a/pkg/loader/yaml.go b/pkg/loader/yaml.go index f697bfa0..c0e8a339 100644 --- a/pkg/loader/yaml.go +++ b/pkg/loader/yaml.go @@ -1,23 +1,33 @@ package loader -import ( - "fmt" +// import ( +// "fmt" - "gopkg.in/yaml.v3" -) +// "gopkg.in/yaml.v3" +// ) -func YamlLoaderFactory(path string, contents []byte) (Loader, error) { - d := &yamlDetector{} - if err := yaml.Unmarshal(contents, d); err != nil { - return nil, fmt.Errorf("Unable to parse %v as yaml: %v", path, err) - } - if d.AWSTemplateFormatVersion != "" { - return CfnYamlLoaderFactory(path, contents) - } +var YamlTypeDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i InputFile) (Loader, error) { + if i.Ext != ".yaml" && i.Ext != ".yml" { + return nil, nil + } - return nil, fmt.Errorf("Unrecognized yaml file: %v", path) -} + return i.DetectType(CfnYamlDetector) + }, +}) -type yamlDetector struct { - AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` -} +// func YamlLoaderFactory(path string, contents []byte) (Loader, error) { +// d := &yamlDetector{} +// if err := yaml.Unmarshal(contents, d); err != nil { +// return nil, fmt.Errorf("Unable to parse %v as yaml: %v", path, err) +// } +// if d.AWSTemplateFormatVersion != "" { +// return CfnYamlLoaderFactory(path, contents) +// } + +// return nil, fmt.Errorf("Unrecognized yaml file: %v", path) +// } + +// type yamlDetector struct { +// AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` +// } diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index a38da2bb..ed2a9b36 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -114,7 +114,7 @@ type Summary struct { Severities map[string]int `json:"severities"` } -func ParseRegulaOutput(r rego.Result) (*RegulaOutput, error) { +func ParseRegulaOutput(_ *loader.LoadedFiles, r rego.Result) (*RegulaOutput, error) { j, err := json.Marshal(r.Expressions[0].Value) if err != nil { return nil, err @@ -126,4 +126,4 @@ func ParseRegulaOutput(r rego.Result) (*RegulaOutput, error) { return output, nil } -type Reporter func(l *loader.LoadedFiles, r *RegulaOutput) (string, error) +type Reporter func(r *RegulaOutput) (string, error) diff --git a/pkg/reporter/json.go b/pkg/reporter/json.go index b9e92acb..d58c8c0a 100644 --- a/pkg/reporter/json.go +++ b/pkg/reporter/json.go @@ -3,11 +3,9 @@ package reporter import ( "bytes" "encoding/json" - - "github.com/fugue/regula/pkg/loader" ) -func JsonReporter(_ *loader.LoadedFiles, r *RegulaOutput) (string, error) { +func JsonReporter(r *RegulaOutput) (string, error) { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetEscapeHTML(false) diff --git a/pkg/reporter/junit.go b/pkg/reporter/junit.go index 29c1bcf8..2a57e7fb 100644 --- a/pkg/reporter/junit.go +++ b/pkg/reporter/junit.go @@ -4,11 +4,9 @@ import ( "encoding/xml" "fmt" "sort" - - "github.com/fugue/regula/pkg/loader" ) -func JUnitReporter(l *loader.LoadedFiles, o *RegulaOutput) (string, error) { +func JUnitReporter(o *RegulaOutput) (string, error) { byFilepath := map[string]map[string]ResourceResults{} for _, r := range o.RuleResults { fileResults, ok := byFilepath[r.Filepath] diff --git a/pkg/reporter/table.go b/pkg/reporter/table.go index 899ab1e5..0c44834f 100644 --- a/pkg/reporter/table.go +++ b/pkg/reporter/table.go @@ -5,10 +5,9 @@ import ( "github.com/alexeyco/simpletable" "github.com/fatih/color" - "github.com/fugue/regula/pkg/loader" ) -func TableReporter(l *loader.LoadedFiles, o *RegulaOutput) (string, error) { +func TableReporter(o *RegulaOutput) (string, error) { tableData := []TableRow{} var overall string if o.Summary.RuleResults["FAIL"] > 0 { From 41d0931e9443f96815e5adc3cc9abba4e499fb02 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 27 Apr 2021 16:12:23 -0400 Subject: [PATCH 17/65] [RM-5352] Add TAP reporter --- pkg/reporter/base.go | 75 ++++++++++++++ pkg/reporter/get-reporter.go | 2 + pkg/reporter/junit.go | 185 ++++++++++++++--------------------- pkg/reporter/tap.go | 55 +++++++++++ 4 files changed, 206 insertions(+), 111 deletions(-) create mode 100644 pkg/reporter/tap.go diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index ed2a9b36..2fcc448d 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -2,6 +2,7 @@ package reporter import ( "encoding/json" + "sort" "github.com/fugue/regula/pkg/loader" "github.com/open-policy-agent/opa/rego" @@ -82,6 +83,68 @@ func (o RegulaOutput) ExceedsSeverity(severity Severity) bool { return maxSeverity >= severity } +type ResourceResults struct { + ResourceId string + ResourceType string + Results []RuleResult + Pass bool +} + +type FilepathResults struct { + Filepath string + Results map[string]ResourceResults + Pass bool +} + +func (f FilepathResults) SortedKeys() []string { + keys := []string{} + for k := range f.Results { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +type ResultsByFilepath map[string]FilepathResults + +func (r ResultsByFilepath) SortedKeys() []string { + keys := []string{} + for k := range r { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +func (o RegulaOutput) AggregateByFilepath() ResultsByFilepath { + byFilepath := ResultsByFilepath{} + for _, r := range o.RuleResults { + filepathResults, ok := byFilepath[r.Filepath] + if !ok { + filepathResults = FilepathResults{ + Filepath: r.Filepath, + Results: map[string]ResourceResults{}, + Pass: !r.IsFail(), + } + } + resourceResults, ok := filepathResults.Results[r.ResourceId] + if !ok { + resourceResults = ResourceResults{ + ResourceId: r.ResourceId, + ResourceType: r.ResourceType, + Results: []RuleResult{}, + Pass: !r.IsFail(), + } + } + resourceResults.Results = append(resourceResults.Results, r) + resourceResults.Pass = resourceResults.Pass && !r.IsFail() + filepathResults.Results[r.ResourceId] = resourceResults + filepathResults.Pass = filepathResults.Pass && resourceResults.Pass + byFilepath[r.Filepath] = filepathResults + } + return byFilepath +} + type RuleResult struct { Controls []string `json:"controls"` Filepath string `json:"filepath"` @@ -98,6 +161,18 @@ type RuleResult struct { RuleSummary string `json:"rule_summary"` } +func (r RuleResult) IsWaived() bool { + return r.RuleResult == "WAIVED" +} + +func (r RuleResult) IsPass() bool { + return r.RuleResult == "PASS" +} + +func (r RuleResult) IsFail() bool { + return r.RuleResult == "FAIL" +} + func (r RuleResult) Message() string { if r.RuleMessage != "" { return r.RuleMessage diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/get-reporter.go index e1a54410..1ec617f2 100644 --- a/pkg/reporter/get-reporter.go +++ b/pkg/reporter/get-reporter.go @@ -10,6 +10,8 @@ func GetReporter(format Format) (Reporter, error) { return TableReporter, nil case Junit: return JUnitReporter, nil + case Tap: + return TapReporter, nil default: return nil, fmt.Errorf("Unsupported or unrecognized reporter: %v", FormatIds[format]) } diff --git a/pkg/reporter/junit.go b/pkg/reporter/junit.go index 2a57e7fb..65fb4134 100644 --- a/pkg/reporter/junit.go +++ b/pkg/reporter/junit.go @@ -7,90 +7,72 @@ import ( ) func JUnitReporter(o *RegulaOutput) (string, error) { - byFilepath := map[string]map[string]ResourceResults{} - for _, r := range o.RuleResults { - fileResults, ok := byFilepath[r.Filepath] - if !ok { - fileResults = map[string]ResourceResults{} - } - resourceResults, ok := fileResults[r.ResourceId] - if !ok { - resourceResults = ResourceResults{ - Results: []RuleResult{}, - ResourceId: r.ResourceId, - ResourceType: r.ResourceType, - } - } - resourceResults.Results = append(resourceResults.Results, r) - fileResults[r.ResourceId] = resourceResults - byFilepath[r.Filepath] = fileResults - } - filePaths := []string{} - for k := range byFilepath { - filePaths = append(filePaths, k) + testSuites := o.AggregateByFilepath().ToTestSuites() + x, err := xml.MarshalIndent(testSuites, "", " ") + if err != nil { + return "", err } - sort.Strings(filePaths) - testSuites := []TestSuite{} - for _, f := range filePaths { - resourceIds := []string{} - for k := range byFilepath[f] { - resourceIds = append(resourceIds, k) - } - sort.Strings(resourceIds) - testCases := []TestCase{} - for _, r := range resourceIds { - resourceResults := byFilepath[f][r] - sort.SliceStable(resourceResults.Results, func(i, j int) bool { - return resourceResults.Results[i].RuleName < resourceResults.Results[j].RuleName + return string(x), nil +} + +func (r ResourceResults) ToTestCase() JUnitTestCase { + results := r.Results + sort.SliceStable(results, func(i, j int) bool { + return results[i].RuleName < results[j].RuleName + }) + skips := []JUnitSkipMessage{} + failures := []JUnitFailure{} + for _, result := range results { + if result.IsWaived() { + skips = append(skips, JUnitSkipMessage{ + Message: result.Message(), + }) + } else if result.IsFail() { + failures = append(failures, JUnitFailure{ + Message: result.Message(), + Type: result.RuleName, + Contents: failureMessage(result), }) - skips := []TestSkipMessage{} - failures := []TestFailure{} - for _, result := range resourceResults.Results { - if result.RuleResult == "WAIVED" { - skips = append(skips, TestSkipMessage{ - Message: result.Message(), - }) - } else if result.RuleResult == "FAIL" { - failures = append(failures, TestFailure{ - Message: result.Message(), - Type: result.RuleName, - Contents: ruleMessage(result), - }) - } - } - testCase := TestCase{ - Name: r, - ClassName: resourceResults.ResourceType, - Assertions: len(resourceResults.Results), - } - if len(skips) > 0 { - testCase.SkipMessage = &skips - } - if len(failures) > 0 { - testCase.Failures = &failures - } - testCases = append(testCases, testCase) } - testSuites = append(testSuites, TestSuite{ - Name: f, - Tests: len(testCases), - TestCases: testCases, - }) } + testCase := JUnitTestCase{ + Name: r.ResourceId, + ClassName: r.ResourceType, + Assertions: len(r.Results), + } + if len(skips) > 0 { + testCase.SkipMessage = &skips + } + if len(failures) > 0 { + testCase.Failures = &failures + } + return testCase +} - rootNode := TestSuites{ - Name: "Regula", - TestSuites: testSuites, +func (r FilepathResults) ToTestSuite() JUnitTestSuite { + testCases := []JUnitTestCase{} + for _, k := range r.SortedKeys() { + testCases = append(testCases, r.Results[k].ToTestCase()) } + return JUnitTestSuite{ + Name: r.Filepath, + Tests: len(testCases), + TestCases: testCases, + } +} - x, err := xml.MarshalIndent(rootNode, "", " ") - if err != nil { - return "", err +func (r ResultsByFilepath) ToTestSuites() JUnitTestSuites { + testSuites := []JUnitTestSuite{} + for _, k := range r.SortedKeys() { + testSuites = append(testSuites, r[k].ToTestSuite()) + } + return JUnitTestSuites{ + Name: "Regula", + TestSuites: testSuites, } - return string(x), nil } -func ruleMessage(r RuleResult) string { +func failureMessage(r RuleResult) string { return fmt.Sprintf( "Rule ID: %v\nRule Name: %v\nSeverity: %v\nMessage: %v", r.RuleId, @@ -100,53 +82,34 @@ func ruleMessage(r RuleResult) string { ) } -func sortedKeys(m map[string]interface{}) []string { - keys := []string{} - for k := range m { - keys = append(keys, k) - } - sort.Strings(keys) - return keys -} - -type ResultsByFilepath struct { - Filepaths map[string]map[string]ResourceResults -} - -type ResourceResults struct { - Results []RuleResult - ResourceId string - ResourceType string -} - -type TestSuites struct { - XMLName xml.Name `xml:"testsuites"` - Name string `xml:"name,attr"` - TestSuites []TestSuite `xml:"testsuite"` +type JUnitTestSuites struct { + XMLName xml.Name `xml:"testsuites"` + Name string `xml:"name,attr"` + TestSuites []JUnitTestSuite `xml:"testsuite"` } -type TestSuite struct { - XMLName xml.Name `xml:"testsuite"` - Name string `xml:"name,attr"` - Tests int `xml:"tests,attr"` - TestCases []TestCase `xml:"testcase"` +type JUnitTestSuite struct { + XMLName xml.Name `xml:"testsuite"` + Name string `xml:"name,attr"` + Tests int `xml:"tests,attr"` + TestCases []JUnitTestCase `xml:"testcase"` } -type TestCase struct { - XMLName xml.Name `xml:"testcase"` - Name string `xml:"name,attr"` - ClassName string `xml:"classname,attr"` - Assertions int `xml:"assertions,attr"` - SkipMessage *[]TestSkipMessage `xml:"skipped,omitempty"` - Failures *[]TestFailure `xml:"error,omitempty"` +type JUnitTestCase struct { + XMLName xml.Name `xml:"testcase"` + Name string `xml:"name,attr"` + ClassName string `xml:"classname,attr"` + Assertions int `xml:"assertions,attr"` + SkipMessage *[]JUnitSkipMessage `xml:"skipped,omitempty"` + Failures *[]JUnitFailure `xml:"error,omitempty"` } -type TestFailure struct { +type JUnitFailure struct { Message string `xml:"message,attr"` Type string `xml:"type,attr"` Contents string `xml:",chardata"` } -type TestSkipMessage struct { +type JUnitSkipMessage struct { Message string `xml:"message,attr"` } diff --git a/pkg/reporter/tap.go b/pkg/reporter/tap.go new file mode 100644 index 00000000..eb61ae34 --- /dev/null +++ b/pkg/reporter/tap.go @@ -0,0 +1,55 @@ +package reporter + +import ( + "fmt" + "sort" + "strings" +) + +func TapReporter(o *RegulaOutput) (string, error) { + results := o.RuleResults + sort.SliceStable(results, func(i, j int) bool { + if results[i].ResourceId == results[j].ResourceId { + return results[i].RuleId < results[j].RuleId + } + return results[i].ResourceId < results[j].ResourceId + }) + + tapOutput := []string{} + for idx, r := range results { + tapOutput = append(tapOutput, r.ToTapRow(idx).String("")) + } + + return strings.Join(tapOutput, "\n"), nil +} + +type TapRow struct { + Ok string + Index int + Message string + Directive string + Resource string + RuleID string +} + +func (r TapRow) String(indent string) string { + return fmt.Sprintf("%s%s %d %s: %s%s", indent, r.Ok, r.Index, r.Resource, r.Message, r.Directive) +} + +func (r RuleResult) ToTapRow(idx int) TapRow { + ok := "ok" + if r.IsFail() { + ok = "not ok" + } + directive := "" + if r.IsWaived() { + directive = " # SKIP: rule waived" + } + return TapRow{ + Ok: ok, + Index: idx, + Message: r.Message(), + Directive: directive, + Resource: r.ResourceId, + } +} From b13cabea4ea2d0f0420d5f0889e7583d562dd274 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 10:38:51 -0400 Subject: [PATCH 18/65] [RM-5352] Implement gitignore functionality --- cmd/run.go | 16 ++++++----- go.mod | 3 ++- go.sum | 13 +++++++-- pkg/git/repo.go | 60 +++++++++++++++++++++++++++++++++++++++++ pkg/loader/base.go | 37 ++++++++++++++++++++----- pkg/loader/loadpaths.go | 11 +++++++- pkg/reporter/junit.go | 2 +- 7 files changed, 124 insertions(+), 18 deletions(-) create mode 100644 pkg/git/repo.go diff --git a/cmd/run.go b/cmd/run.go index bf02ef16..21509117 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -31,6 +31,11 @@ func NewRunCommand() *cobra.Command { fmt.Println(err) os.Exit(1) } + noIgnore, err := cmd.Flags().GetBool("no-ignore") + if err != nil { + fmt.Println(err) + os.Exit(1) + } ctx := context.TODO() ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ Ctx: ctx, @@ -45,12 +50,9 @@ func NewRunCommand() *cobra.Command { if len(paths) < 1 { if terminal.IsTerminal(int(os.Stdin.Fd())) { - wd, err := os.Getwd() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - paths = []string{wd} + // Not using os.Getwd here so that we get relative paths. + // A single dot should mean the same on windows. + paths = []string{"."} } else { paths = []string{"-"} } @@ -59,6 +61,7 @@ func NewRunCommand() *cobra.Command { loadedFiles, err := loader.LoadPaths(loader.LoadPathsOptions{ Paths: paths, InputType: inputType, + NoIgnore: noIgnore, }) if err != nil { fmt.Println(err) @@ -92,6 +95,7 @@ func NewRunCommand() *cobra.Command { cmd.Flags().StringSliceP("include", "i", nil, "Select rego libraries to include") cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") + cmd.Flags().BoolP("no-ignore", "n", false, "Disable .gitignore rules") cmd.Flags().VarP( enumflag.New(&inputType, "input-type", loader.InputTypeIds, enumflag.EnumCaseInsensitive), "input-type", "t", diff --git a/go.mod b/go.mod index 6742c350..2b73474c 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,12 @@ require ( github.com/alexeyco/simpletable v1.0.0 github.com/fatih/color v1.7.0 github.com/kr/text v0.2.0 // indirect + github.com/libgit2/git2go/v31 v31.4.14 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 github.com/spf13/cobra v1.1.3 github.com/thediveo/enumflag v0.10.1 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) diff --git a/go.sum b/go.sum index ee427dd2..b64ca56d 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -196,6 +198,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/libgit2/git2go/v31 v31.4.14 h1:6GOd3965D9e/+gjxCwZF4eQ+vB9kKB4yKFqdQr6XZ2E= +github.com/libgit2/git2go/v31 v31.4.14/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -383,8 +387,9 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -458,13 +463,17 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/pkg/git/repo.go b/pkg/git/repo.go new file mode 100644 index 00000000..9f8404f4 --- /dev/null +++ b/pkg/git/repo.go @@ -0,0 +1,60 @@ +package git + +import ( + "os" + "path/filepath" + + git "github.com/libgit2/git2go/v31" +) + +type GitRepoFinder struct { + cache map[string]*git.Repository +} + +func NewGitRepoFinder() *GitRepoFinder { + return &GitRepoFinder{ + cache: map[string]*git.Repository{}, + } +} + +func (s *GitRepoFinder) FindRepo(path string) *git.Repository { + absPath, err := filepath.Abs(path) + if err != nil { + return nil + } + lastPath := "" + traversedPaths := []string{} + for absPath != lastPath { + if foundRepo, ok := s.cache[absPath]; ok { + return foundRepo + } + entries, err := os.ReadDir(absPath) + if err != nil { + // Store nil so that we don't retry this operation when child dirs are + // passed in. + s.cache[absPath] = nil + return nil + } + for _, e := range entries { + if e.Name() == ".git" { + repo, err := git.OpenRepository(filepath.Join(absPath, e.Name())) + if err != nil { + s.cache[absPath] = nil + return nil + } + s.cache[absPath] = repo + return repo + } + } + traversedPaths = append(traversedPaths, absPath) + lastPath = absPath + absPath = filepath.Dir(absPath) + } + // At this point we've traversed to the top of the tree and haven't found + // anything. We'll cache all traversed paths so that we don't repeat the + // list operations for child dirs. + for _, p := range traversedPaths { + s.cache[p] = nil + } + return nil +} diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 7b1660ce..b959b2dc 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -4,6 +4,9 @@ import ( "io" "os" "path/filepath" + + "github.com/fugue/regula/pkg/git" + git2go "github.com/libgit2/git2go/v31" ) type InputType int @@ -100,19 +103,39 @@ func (i InputDirectory) GetPath() string { return i.Path } -func NewInputDirectory(path string, name string) (*InputDirectory, error) { - // name := filepath.Base(path) +type NewInputDirectoryOptions struct { + Path string + Name string + NoIgnore bool + GitRepoFinder *git.GitRepoFinder +} + +func NewInputDirectory(opts NewInputDirectoryOptions) (*InputDirectory, error) { contents := []InputPath{} - entries, err := os.ReadDir(path) + entries, err := os.ReadDir(opts.Path) if err != nil { return nil, err } + var repo *git2go.Repository + if !opts.NoIgnore { + repo = opts.GitRepoFinder.FindRepo(opts.Path) + } for _, e := range entries { n := e.Name() - p := filepath.Join(path, n) + p := filepath.Join(opts.Path, n) + if repo != nil { + if ignored, _ := repo.IsPathIgnored(p); ignored { + continue + } + } var i InputPath if e.IsDir() { - i, err = NewInputDirectory(p, n) + i, err = NewInputDirectory(NewInputDirectoryOptions{ + Path: p, + Name: n, + NoIgnore: opts.NoIgnore, + GitRepoFinder: opts.GitRepoFinder, + }) if err != nil { return nil, err } @@ -123,8 +146,8 @@ func NewInputDirectory(path string, name string) (*InputDirectory, error) { contents = append(contents, i) } return &InputDirectory{ - Path: path, - Name: name, + Path: opts.Path, + Name: opts.Name, Contents: contents, }, nil } diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 288e4a05..a3b6520f 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "sort" + + "github.com/fugue/regula/pkg/git" ) type LoadedFiles struct { @@ -27,6 +29,7 @@ func (l *LoadedFiles) RegulaInput() []RegulaInput { type LoadPathsOptions struct { Paths []string InputType InputType + NoIgnore bool } func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { @@ -43,6 +46,7 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { } return nil } + gitRepoFinder := git.NewGitRepoFinder() for _, path := range options.Paths { if path == "-" { loader, err := loaderFactory(InputFile{ @@ -65,7 +69,12 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { } var i InputPath if info.IsDir() { - i, err = NewInputDirectory(path, name) + i, err = NewInputDirectory(NewInputDirectoryOptions{ + Path: path, + Name: name, + NoIgnore: options.NoIgnore, + GitRepoFinder: gitRepoFinder, + }) if err != nil { return nil, err } diff --git a/pkg/reporter/junit.go b/pkg/reporter/junit.go index 65fb4134..6f9536d7 100644 --- a/pkg/reporter/junit.go +++ b/pkg/reporter/junit.go @@ -101,7 +101,7 @@ type JUnitTestCase struct { ClassName string `xml:"classname,attr"` Assertions int `xml:"assertions,attr"` SkipMessage *[]JUnitSkipMessage `xml:"skipped,omitempty"` - Failures *[]JUnitFailure `xml:"error,omitempty"` + Failures *[]JUnitFailure `xml:"failure,omitempty"` } type JUnitFailure struct { From 51f501326f26f387c34e207ae6ed6553d948aad5 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 12:37:58 -0400 Subject: [PATCH 19/65] [RM-5352] Better type detector implementation --- cmd/run.go | 14 ++++- go.mod | 1 + go.sum | 3 + pkg/loader/base.go | 69 +++++++++++++-------- pkg/loader/cfnyaml.go | 49 +++++---------- pkg/loader/json.go | 114 +++++++++++++---------------------- pkg/loader/loadpaths.go | 76 +++++++++++------------ pkg/loader/yaml.go | 28 ++------- pkg/rego/runner.go | 1 + pkg/reporter/base.go | 2 + pkg/reporter/get-reporter.go | 6 ++ 11 files changed, 167 insertions(+), 196 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 21509117..72106b2d 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -8,6 +8,7 @@ import ( "github.com/fugue/regula/pkg/loader" "github.com/fugue/regula/pkg/rego" "github.com/fugue/regula/pkg/reporter" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/thediveo/enumflag" "golang.org/x/crypto/ssh/terminal" @@ -36,6 +37,14 @@ func NewRunCommand() *cobra.Command { fmt.Println(err) os.Exit(1) } + debug, err := cmd.Flags().GetBool("debug") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + if debug { + log.SetLevel(log.DebugLevel) + } ctx := context.TODO() ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ Ctx: ctx, @@ -86,7 +95,9 @@ func NewRunCommand() *cobra.Command { fmt.Println(err) os.Exit(1) } - fmt.Println(report) + if report != "" { + fmt.Println(report) + } if output.ExceedsSeverity(severity) { os.Exit(1) } @@ -96,6 +107,7 @@ func NewRunCommand() *cobra.Command { cmd.Flags().StringSliceP("include", "i", nil, "Select rego libraries to include") cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") cmd.Flags().BoolP("no-ignore", "n", false, "Disable .gitignore rules") + cmd.Flags().BoolP("debug", "d", false, "Enable debug output") cmd.Flags().VarP( enumflag.New(&inputType, "input-type", loader.InputTypeIds, enumflag.EnumCaseInsensitive), "input-type", "t", diff --git a/go.mod b/go.mod index 2b73474c..746e05b9 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/libgit2/git2go/v31 v31.4.14 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 + github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v1.1.3 github.com/thediveo/enumflag v0.10.1 golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c diff --git a/go.sum b/go.sum index b64ca56d..e598dc8e 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -276,6 +277,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -347,6 +349,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/thediveo/enumflag v0.10.1 h1:DB3Ag69VZ7BCv6jzKECrZ0ebZrHLzFRMIFYt96s4OxM= diff --git a/pkg/loader/base.go b/pkg/loader/base.go index b959b2dc..4a37f0e7 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -44,18 +44,18 @@ type LocationAwareLoader interface { type LoaderFactory func(inputPath InputPath) (Loader, error) type TypeDetector struct { - DetectDirectory func(i InputDirectory) (Loader, error) - DetectFile func(i InputFile) (Loader, error) + DetectDirectory func(i *InputDirectory) (Loader, error) + DetectFile func(i *InputFile) (Loader, error) } func NewTypeDetector(t *TypeDetector) *TypeDetector { if t.DetectDirectory == nil { - t.DetectDirectory = func(i InputDirectory) (Loader, error) { + t.DetectDirectory = func(i *InputDirectory) (Loader, error) { return nil, nil } } if t.DetectFile == nil { - t.DetectFile = func(i InputFile) (Loader, error) { + t.DetectFile = func(i *InputFile) (Loader, error) { return nil, nil } } @@ -64,35 +64,35 @@ func NewTypeDetector(t *TypeDetector) *TypeDetector { type InputPath interface { DetectType(d TypeDetector) (Loader, error) - Children() []InputPath + Children() []*InputPath IsDir() bool - Walk(w func(i InputPath) error) error + Walk(w func(i *InputPath) error) error GetPath() string } type InputDirectory struct { Path string Name string - Contents []InputPath + Contents []*InputPath } -func (i InputDirectory) DetectType(d TypeDetector) (Loader, error) { +func (i *InputDirectory) DetectType(d TypeDetector) (Loader, error) { return d.DetectDirectory(i) } -func (i InputDirectory) Children() []InputPath { +func (i *InputDirectory) Children() []*InputPath { return i.Contents } -func (i InputDirectory) IsDir() bool { +func (i *InputDirectory) IsDir() bool { return true } -func (i InputDirectory) Walk(w func(i InputPath) error) error { +func (i *InputDirectory) Walk(w func(i *InputPath) error) error { for _, c := range i.Contents { if err := w(c); err != nil { return err } - if err := c.Walk(w); err != nil { + if err := (*c).Walk(w); err != nil { return err } } @@ -111,7 +111,7 @@ type NewInputDirectoryOptions struct { } func NewInputDirectory(opts NewInputDirectoryOptions) (*InputDirectory, error) { - contents := []InputPath{} + contents := []*InputPath{} entries, err := os.ReadDir(opts.Path) if err != nil { return nil, err @@ -143,7 +143,7 @@ func NewInputDirectory(opts NewInputDirectoryOptions) (*InputDirectory, error) { } else { i = NewInputFile(p, n) } - contents = append(contents, i) + contents = append(contents, &i) } return &InputDirectory{ Path: opts.Path, @@ -153,37 +153,54 @@ func NewInputDirectory(opts NewInputDirectoryOptions) (*InputDirectory, error) { } type InputFile struct { - Path string - Name string - Ext string + Path string + Name string + Ext string + cachedContents []byte } -func (i InputFile) DetectType(d TypeDetector) (Loader, error) { +func (i *InputFile) DetectType(d TypeDetector) (Loader, error) { return d.DetectFile(i) } -func (i InputFile) Children() []InputPath { +func (i *InputFile) Children() []*InputPath { return nil } -func (i InputFile) IsDir() bool { +func (i *InputFile) IsDir() bool { return false } -func (i InputFile) Walk(w func(i InputPath) error) error { +func (i *InputFile) Walk(w func(i *InputPath) error) error { return nil } -func (i InputFile) GetPath() string { +func (i *InputFile) GetPath() string { return i.Path } -func (i InputFile) ReadContents() ([]byte, error) { - if i.Name == "-" { - return io.ReadAll(os.Stdin) +func (i *InputFile) ReadContents() ([]byte, error) { + if i.cachedContents != nil { + return i.cachedContents, nil } - return os.ReadFile(i.Path) + if i.Name == "-" { + contents, err := io.ReadAll(os.Stdin) + if err != nil { + i.cachedContents = []byte{} + return nil, err + } + i.cachedContents = contents + return contents, nil + } else { + contents, err := os.ReadFile(i.Path) + if err != nil { + i.cachedContents = []byte{} + return nil, err + } + i.cachedContents = contents + return contents, nil + } } func NewInputFile(path string, name string) *InputFile { diff --git a/pkg/loader/cfnyaml.go b/pkg/loader/cfnyaml.go index 4c135528..c38d58cd 100644 --- a/pkg/loader/cfnyaml.go +++ b/pkg/loader/cfnyaml.go @@ -7,52 +7,31 @@ import ( "gopkg.in/yaml.v3" ) -var CfnYamlDetector = *NewTypeDetector(&TypeDetector{ - DetectFile: func(i InputFile) (Loader, error) { +var CfnYamlDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i *InputFile) (Loader, error) { contents, err := i.ReadContents() if err != nil { return nil, err } - c := &struct { - AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` - }{} - if err := yaml.Unmarshal(contents, c); err != nil { + + template := &cfnTemplate{} + if err := yaml.Unmarshal(contents, &template); err != nil { return nil, fmt.Errorf("Failed to parse YAML file %v: %v", i.Path, err) } - if c.AWSTemplateFormatVersion != "" { - return baseCfnYamlLoaderFactory(i.Path, contents) + _, hasTemplateFormatVersion := template.Contents["AWSTemplateFormatVersion"] + _, hasResources := template.Contents["Resources"] + + if !hasTemplateFormatVersion && !hasResources { + return nil, fmt.Errorf("Input file is not CloudFormation YAML: %v", i.Path) } - return nil, fmt.Errorf("Input file is not CloudFormation: %v", i.Path) + return &cfnYamlLoader{ + path: i.Path, + template: *template, + }, nil }, }) -func CfnYamlLoaderFactory(i InputPath) (Loader, error) { - if i.IsDir() { - return nil, nil - } - f, ok := i.(InputFile) - if !ok { - return nil, fmt.Errorf("Unable to cast input as file: %v", i.GetPath()) - } - contents, err := f.ReadContents() - if err != nil { - return nil, err - } - return baseCfnYamlLoaderFactory(f.Path, contents) -} - -func baseCfnYamlLoaderFactory(path string, contents []byte) (Loader, error) { - template := &cfnTemplate{} - if err := yaml.Unmarshal(contents, &template); err != nil { - return nil, fmt.Errorf("Failed to unmarshal CloudFormation YAML file: %v", err) - } - return &cfnYamlLoader{ - path: path, - template: *template, - }, nil -} - type cfnYamlLoader struct { path string template cfnTemplate diff --git a/pkg/loader/json.go b/pkg/loader/json.go index e342f6f8..c60ea42f 100644 --- a/pkg/loader/json.go +++ b/pkg/loader/json.go @@ -6,99 +6,69 @@ import ( ) var JsonTypeDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i InputFile) (Loader, error) { + DetectFile: func(i *InputFile) (Loader, error) { if i.Ext != ".json" { - return nil, nil + return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) } - contents, err := i.ReadContents() - if err != nil { - return nil, err + l, err := i.DetectType(*TfPlanDetector) + if err == nil { + return l, nil } - j := &struct { - AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` - TerraformVersion string `json:"terraform_version"` - }{} - if err := json.Unmarshal(contents, j); err != nil { - return nil, err + l, err = i.DetectType(*CfnJsonDetector) + if err == nil { + return l, nil } - if j.AWSTemplateFormatVersion == "" && j.TerraformVersion == "" { - return nil, nil - } - return baseJsonLoaderFactory(i.Path, contents) + return nil, fmt.Errorf("JSON file %v not a recognized type", i.Path) }, }) -var TfPlanDetector = *NewTypeDetector(&TypeDetector{ - DetectFile: func(i InputFile) (Loader, error) { +var TfPlanDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i *InputFile) (Loader, error) { contents, err := i.ReadContents() if err != nil { return nil, err } - return baseTfPlanDetector(i.Path, contents) + c := &map[string]interface{}{} + if err := json.Unmarshal(contents, c); err != nil { + return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) + } + _, hasTerraformVersion := (*c)["terraform_version"] + + if !hasTerraformVersion { + return nil, fmt.Errorf("Input file is not Terraform Plan JSON: %v", i.Path) + } + + return &jsonLoader{ + path: i.Path, + content: c, + }, nil }, }) -func baseTfPlanDetector(path string, contents []byte) (Loader, error) { - j := &struct { - TerraformVersion string `json:"terraform_version"` - }{} - if err := json.Unmarshal(contents, j); err != nil { - return nil, err - } - if j.TerraformVersion == "" { - return nil, nil - } - return baseJsonLoaderFactory(path, contents) -} - -var CfnJsonDetector = *NewTypeDetector(&TypeDetector{ - DetectFile: func(i InputFile) (Loader, error) { +var CfnJsonDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i *InputFile) (Loader, error) { contents, err := i.ReadContents() if err != nil { return nil, err } - return baseCfnJsonDetector(i.Path, contents) - }, -}) -func baseCfnJsonDetector(path string, contents []byte) (Loader, error) { - j := &struct { - AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` - }{} - if err := json.Unmarshal(contents, j); err != nil { - return nil, err - } - if j.AWSTemplateFormatVersion == "" { - return nil, nil - } - return baseJsonLoaderFactory(path, contents) -} + c := &map[string]interface{}{} + if err := json.Unmarshal(contents, c); err != nil { + return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) + } + _, hasTemplateFormatVersion := (*c)["AWSTemplateFormatVersion"] + _, hasResources := (*c)["Resources"] -func JsonLoaderFactory(i InputPath) (Loader, error) { - if i.IsDir() { - return nil, nil - } - f, ok := i.(InputFile) - if !ok { - return nil, fmt.Errorf("Unable to cast input as file: %v", i.GetPath()) - } - contents, err := f.ReadContents() - if err != nil { - return nil, err - } - return baseJsonLoaderFactory(f.Path, contents) -} + if !hasTemplateFormatVersion && !hasResources { + return nil, fmt.Errorf("Input file is not CloudFormation JSON: %v", i.Path) + } -func baseJsonLoaderFactory(path string, contents []byte) (Loader, error) { - c := &map[string]interface{}{} - if err := json.Unmarshal(contents, c); err != nil { - return nil, fmt.Errorf("Unable to parse %v as recognized JSON type: %v", path, err) - } - return &jsonLoader{ - path: path, - content: c, - }, nil -} + return &jsonLoader{ + path: i.Path, + content: c, + }, nil + }, +}) type jsonLoader struct { path string diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index a3b6520f..b0021929 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -34,26 +34,27 @@ type LoadPathsOptions struct { func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { loaders := map[string]Loader{} - loaderFactory, err := loaderFactoryByInputType(options.InputType) + detector, err := detectorByInputType(options.InputType) if err != nil { return nil, err } - walkFunc := func(i InputPath) error { + walkFunc := func(i *InputPath) error { // Ignore errors when we're recursing - loader, _ := loaderFactory(i) + loader, _ := (*i).DetectType(*detector) if loader != nil { - loaders[i.GetPath()] = loader + loaders[(*i).GetPath()] = loader } return nil } gitRepoFinder := git.NewGitRepoFinder() for _, path := range options.Paths { if path == "-" { - loader, err := loaderFactory(InputFile{ + i := InputFile{ Path: "", Name: "-", Ext: "", - }) + } + loader, err := i.DetectType(*detector) if err != nil { return nil, err } @@ -81,7 +82,7 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { } else { i = NewInputFile(path, name) } - loader, err := loaderFactory(i) + loader, err := i.DetectType(*detector) if err != nil { return nil, err } @@ -101,50 +102,49 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { }, nil } -func loaderFactoryByInputType(inputType InputType) (LoaderFactory, error) { +func detectorByInputType(inputType InputType) (*TypeDetector, error) { switch inputType { case Auto: - return autoLoaderFactory, nil + return AutoDetector, nil case CfnYaml: - return CfnYamlLoaderFactory, nil - case CfnJson, TfPlan: - return JsonLoaderFactory, nil + return CfnYamlDetector, nil + case CfnJson: + return CfnJsonDetector, nil + case TfPlan: + return TfPlanDetector, nil default: return nil, fmt.Errorf("Unsupported input type: %v", inputType) } } -func autoLoaderFactory(i InputPath) (Loader, error) { - if i.IsDir() { - return nil, nil - } +var AutoDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i *InputFile) (Loader, error) { + if i.GetPath() == "" { + l, err := i.DetectType(*TfPlanDetector) + if err == nil { + return l, nil + } + l, err = i.DetectType(*CfnJsonDetector) + if err == nil { + return l, nil + } + l, err = i.DetectType(*CfnYamlDetector) + if err == nil { + return l, nil + } + fmt.Println(err) + return nil, fmt.Errorf("Unable to detect input type of data from stdin.") + } - if i.GetPath() == "" { - l, err := JsonLoaderFactory(i) + l, err := i.DetectType(*JsonTypeDetector) if err == nil { return l, nil } - l, err = CfnYamlLoaderFactory(i) + l, err = i.DetectType(*YamlTypeDetector) if err == nil { return l, nil } - return nil, fmt.Errorf("Unable to detect input type of data from stdin.") - } - - l, err := i.DetectType(*YamlTypeDetector) - if err != nil { - return nil, err - } - if l != nil { - return l, nil - } - l, err = i.DetectType(*JsonTypeDetector) - if err != nil { - return nil, err - } - if l != nil { - return l, nil - } - return nil, fmt.Errorf("Unable to detect file type for file: %s", i.GetPath()) -} + return nil, fmt.Errorf("Unable to detect file type for file: %s", i.GetPath()) + }, +}) diff --git a/pkg/loader/yaml.go b/pkg/loader/yaml.go index c0e8a339..643cb51d 100644 --- a/pkg/loader/yaml.go +++ b/pkg/loader/yaml.go @@ -1,33 +1,13 @@ package loader -// import ( -// "fmt" - -// "gopkg.in/yaml.v3" -// ) +import "fmt" var YamlTypeDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i InputFile) (Loader, error) { + DetectFile: func(i *InputFile) (Loader, error) { if i.Ext != ".yaml" && i.Ext != ".yml" { - return nil, nil + return nil, fmt.Errorf("File does not have .yaml or .yml extension: %v", i.Path) } - return i.DetectType(CfnYamlDetector) + return i.DetectType(*CfnYamlDetector) }, }) - -// func YamlLoaderFactory(path string, contents []byte) (Loader, error) { -// d := &yamlDetector{} -// if err := yaml.Unmarshal(contents, d); err != nil { -// return nil, fmt.Errorf("Unable to parse %v as yaml: %v", path, err) -// } -// if d.AWSTemplateFormatVersion != "" { -// return CfnYamlLoaderFactory(path, contents) -// } - -// return nil, fmt.Errorf("Unrecognized yaml file: %v", path) -// } - -// type yamlDetector struct { -// AWSTemplateFormatVersion string `yaml:"AWSTemplateFormatVersion"` -// } diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go index f535c4a9..f3f70110 100644 --- a/pkg/rego/runner.go +++ b/pkg/rego/runner.go @@ -30,6 +30,7 @@ type RuleRunnerOptions struct { Ctx context.Context UserOnly bool Includes []string + Debug bool } var LoadExts map[string]bool = map[string]bool{ diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index 2fcc448d..0eb91d9a 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -38,6 +38,7 @@ const ( Junit Tap Tap13 + None ) var FormatIds = map[Format][]string{ @@ -46,6 +47,7 @@ var FormatIds = map[Format][]string{ Junit: {"junit"}, Tap: {"tap"}, Tap13: {"tap13"}, + None: {"none"}, } type RegulaOutput struct { diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/get-reporter.go index 1ec617f2..4261055e 100644 --- a/pkg/reporter/get-reporter.go +++ b/pkg/reporter/get-reporter.go @@ -12,7 +12,13 @@ func GetReporter(format Format) (Reporter, error) { return JUnitReporter, nil case Tap: return TapReporter, nil + case None: + return noneReporter, nil default: return nil, fmt.Errorf("Unsupported or unrecognized reporter: %v", FormatIds[format]) } } + +func noneReporter(o *RegulaOutput) (string, error) { + return "", nil +} From e3f31ce936901d1cf65b1046edad5521dcb9226b Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 13:16:16 -0400 Subject: [PATCH 20/65] [RM-5352] Better detector implementation --- pkg/loader/base.go | 22 +++++++++++++--------- pkg/loader/cfnyaml.go | 5 ++++- pkg/loader/json.go | 22 ++++++++++++++++------ pkg/loader/loadpaths.go | 37 +++++++++++++++---------------------- pkg/loader/yaml.go | 8 +++++--- 5 files changed, 53 insertions(+), 41 deletions(-) diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 4a37f0e7..7ef462f9 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -43,19 +43,23 @@ type LocationAwareLoader interface { type LoaderFactory func(inputPath InputPath) (Loader, error) +type DetectOptions struct { + IgnoreExt bool +} + type TypeDetector struct { - DetectDirectory func(i *InputDirectory) (Loader, error) - DetectFile func(i *InputFile) (Loader, error) + DetectDirectory func(i *InputDirectory, opts DetectOptions) (Loader, error) + DetectFile func(i *InputFile, opts DetectOptions) (Loader, error) } func NewTypeDetector(t *TypeDetector) *TypeDetector { if t.DetectDirectory == nil { - t.DetectDirectory = func(i *InputDirectory) (Loader, error) { + t.DetectDirectory = func(i *InputDirectory, opts DetectOptions) (Loader, error) { return nil, nil } } if t.DetectFile == nil { - t.DetectFile = func(i *InputFile) (Loader, error) { + t.DetectFile = func(i *InputFile, opts DetectOptions) (Loader, error) { return nil, nil } } @@ -63,7 +67,7 @@ func NewTypeDetector(t *TypeDetector) *TypeDetector { } type InputPath interface { - DetectType(d TypeDetector) (Loader, error) + DetectType(d TypeDetector, opts DetectOptions) (Loader, error) Children() []*InputPath IsDir() bool Walk(w func(i *InputPath) error) error @@ -75,8 +79,8 @@ type InputDirectory struct { Contents []*InputPath } -func (i *InputDirectory) DetectType(d TypeDetector) (Loader, error) { - return d.DetectDirectory(i) +func (i *InputDirectory) DetectType(d TypeDetector, opts DetectOptions) (Loader, error) { + return d.DetectDirectory(i, opts) } func (i *InputDirectory) Children() []*InputPath { @@ -159,8 +163,8 @@ type InputFile struct { cachedContents []byte } -func (i *InputFile) DetectType(d TypeDetector) (Loader, error) { - return d.DetectFile(i) +func (i *InputFile) DetectType(d TypeDetector, opts DetectOptions) (Loader, error) { + return d.DetectFile(i, opts) } func (i *InputFile) Children() []*InputPath { diff --git a/pkg/loader/cfnyaml.go b/pkg/loader/cfnyaml.go index c38d58cd..7d0d85d4 100644 --- a/pkg/loader/cfnyaml.go +++ b/pkg/loader/cfnyaml.go @@ -8,7 +8,10 @@ import ( ) var CfnYamlDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile) (Loader, error) { + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + if !opts.IgnoreExt && i.Ext != ".yaml" && i.Ext != ".yml" { + return nil, fmt.Errorf("File does not have .yaml or .yml extension: %v", i.Path) + } contents, err := i.ReadContents() if err != nil { return nil, err diff --git a/pkg/loader/json.go b/pkg/loader/json.go index c60ea42f..cabc605c 100644 --- a/pkg/loader/json.go +++ b/pkg/loader/json.go @@ -6,15 +6,19 @@ import ( ) var JsonTypeDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile) (Loader, error) { - if i.Ext != ".json" { + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + if !opts.IgnoreExt && i.Ext != ".json" { return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) } - l, err := i.DetectType(*TfPlanDetector) + l, err := i.DetectType(*TfPlanDetector, DetectOptions{ + IgnoreExt: true, + }) if err == nil { return l, nil } - l, err = i.DetectType(*CfnJsonDetector) + l, err = i.DetectType(*CfnJsonDetector, DetectOptions{ + IgnoreExt: true, + }) if err == nil { return l, nil } @@ -23,7 +27,10 @@ var JsonTypeDetector = NewTypeDetector(&TypeDetector{ }) var TfPlanDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile) (Loader, error) { + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + if !opts.IgnoreExt && i.Ext != ".json" { + return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) + } contents, err := i.ReadContents() if err != nil { return nil, err @@ -46,7 +53,10 @@ var TfPlanDetector = NewTypeDetector(&TypeDetector{ }) var CfnJsonDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile) (Loader, error) { + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + if !opts.IgnoreExt && i.Ext != ".json" { + return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) + } contents, err := i.ReadContents() if err != nil { return nil, err diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index b0021929..70358bac 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -40,7 +40,9 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { } walkFunc := func(i *InputPath) error { // Ignore errors when we're recursing - loader, _ := (*i).DetectType(*detector) + loader, _ := (*i).DetectType(*detector, DetectOptions{ + IgnoreExt: false, + }) if loader != nil { loaders[(*i).GetPath()] = loader } @@ -54,7 +56,9 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { Name: "-", Ext: "", } - loader, err := i.DetectType(*detector) + loader, err := i.DetectType(*detector, DetectOptions{ + IgnoreExt: true, + }) if err != nil { return nil, err } @@ -82,7 +86,9 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { } else { i = NewInputFile(path, name) } - loader, err := i.DetectType(*detector) + loader, err := i.DetectType(*detector, DetectOptions{ + IgnoreExt: options.InputType != Auto, + }) if err != nil { return nil, err } @@ -118,29 +124,16 @@ func detectorByInputType(inputType InputType) (*TypeDetector, error) { } var AutoDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile) (Loader, error) { - if i.GetPath() == "" { - l, err := i.DetectType(*TfPlanDetector) - if err == nil { - return l, nil - } - l, err = i.DetectType(*CfnJsonDetector) - if err == nil { - return l, nil - } - l, err = i.DetectType(*CfnYamlDetector) - if err == nil { - return l, nil - } - fmt.Println(err) - return nil, fmt.Errorf("Unable to detect input type of data from stdin.") + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + l, err := i.DetectType(*TfPlanDetector, opts) + if err == nil { + return l, nil } - - l, err := i.DetectType(*JsonTypeDetector) + l, err = i.DetectType(*CfnJsonDetector, opts) if err == nil { return l, nil } - l, err = i.DetectType(*YamlTypeDetector) + l, err = i.DetectType(*CfnYamlDetector, opts) if err == nil { return l, nil } diff --git a/pkg/loader/yaml.go b/pkg/loader/yaml.go index 643cb51d..3286de13 100644 --- a/pkg/loader/yaml.go +++ b/pkg/loader/yaml.go @@ -3,11 +3,13 @@ package loader import "fmt" var YamlTypeDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile) (Loader, error) { - if i.Ext != ".yaml" && i.Ext != ".yml" { + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + if !opts.IgnoreExt && i.Ext != ".yaml" && i.Ext != ".yml" { return nil, fmt.Errorf("File does not have .yaml or .yml extension: %v", i.Path) } - return i.DetectType(*CfnYamlDetector) + return i.DetectType(*CfnYamlDetector, DetectOptions{ + IgnoreExt: true, + }) }, }) From 0481d351cc95f62bfbc53f9a3ceabf867c3218c2 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 13:20:38 -0400 Subject: [PATCH 21/65] [RM-5352] Remove generic yaml and json detectors --- pkg/loader/cfnjson.go | 46 +++++++++++++++++++++ pkg/loader/json.go | 93 ------------------------------------------- pkg/loader/tfplan.go | 44 ++++++++++++++++++++ pkg/loader/yaml.go | 15 ------- 4 files changed, 90 insertions(+), 108 deletions(-) create mode 100644 pkg/loader/cfnjson.go delete mode 100644 pkg/loader/json.go create mode 100644 pkg/loader/tfplan.go delete mode 100644 pkg/loader/yaml.go diff --git a/pkg/loader/cfnjson.go b/pkg/loader/cfnjson.go new file mode 100644 index 00000000..c4e9050d --- /dev/null +++ b/pkg/loader/cfnjson.go @@ -0,0 +1,46 @@ +package loader + +import ( + "encoding/json" + "fmt" +) + +var CfnJsonDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + if !opts.IgnoreExt && i.Ext != ".json" { + return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) + } + contents, err := i.ReadContents() + if err != nil { + return nil, err + } + + c := &map[string]interface{}{} + if err := json.Unmarshal(contents, c); err != nil { + return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) + } + _, hasTemplateFormatVersion := (*c)["AWSTemplateFormatVersion"] + _, hasResources := (*c)["Resources"] + + if !hasTemplateFormatVersion && !hasResources { + return nil, fmt.Errorf("Input file is not CloudFormation JSON: %v", i.Path) + } + + return &cfnJsonLoader{ + path: i.Path, + content: c, + }, nil + }, +}) + +type cfnJsonLoader struct { + path string + content *map[string]interface{} +} + +func (l *cfnJsonLoader) RegulaInput() RegulaInput { + return RegulaInput{ + "filepath": l.path, + "content": l.content, + } +} diff --git a/pkg/loader/json.go b/pkg/loader/json.go deleted file mode 100644 index cabc605c..00000000 --- a/pkg/loader/json.go +++ /dev/null @@ -1,93 +0,0 @@ -package loader - -import ( - "encoding/json" - "fmt" -) - -var JsonTypeDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { - if !opts.IgnoreExt && i.Ext != ".json" { - return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) - } - l, err := i.DetectType(*TfPlanDetector, DetectOptions{ - IgnoreExt: true, - }) - if err == nil { - return l, nil - } - l, err = i.DetectType(*CfnJsonDetector, DetectOptions{ - IgnoreExt: true, - }) - if err == nil { - return l, nil - } - return nil, fmt.Errorf("JSON file %v not a recognized type", i.Path) - }, -}) - -var TfPlanDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { - if !opts.IgnoreExt && i.Ext != ".json" { - return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) - } - contents, err := i.ReadContents() - if err != nil { - return nil, err - } - c := &map[string]interface{}{} - if err := json.Unmarshal(contents, c); err != nil { - return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) - } - _, hasTerraformVersion := (*c)["terraform_version"] - - if !hasTerraformVersion { - return nil, fmt.Errorf("Input file is not Terraform Plan JSON: %v", i.Path) - } - - return &jsonLoader{ - path: i.Path, - content: c, - }, nil - }, -}) - -var CfnJsonDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { - if !opts.IgnoreExt && i.Ext != ".json" { - return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) - } - contents, err := i.ReadContents() - if err != nil { - return nil, err - } - - c := &map[string]interface{}{} - if err := json.Unmarshal(contents, c); err != nil { - return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) - } - _, hasTemplateFormatVersion := (*c)["AWSTemplateFormatVersion"] - _, hasResources := (*c)["Resources"] - - if !hasTemplateFormatVersion && !hasResources { - return nil, fmt.Errorf("Input file is not CloudFormation JSON: %v", i.Path) - } - - return &jsonLoader{ - path: i.Path, - content: c, - }, nil - }, -}) - -type jsonLoader struct { - path string - content *map[string]interface{} -} - -func (l *jsonLoader) RegulaInput() RegulaInput { - return RegulaInput{ - "filepath": l.path, - "content": l.content, - } -} diff --git a/pkg/loader/tfplan.go b/pkg/loader/tfplan.go new file mode 100644 index 00000000..d00b54c8 --- /dev/null +++ b/pkg/loader/tfplan.go @@ -0,0 +1,44 @@ +package loader + +import ( + "encoding/json" + "fmt" +) + +var TfPlanDetector = NewTypeDetector(&TypeDetector{ + DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { + if !opts.IgnoreExt && i.Ext != ".json" { + return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) + } + contents, err := i.ReadContents() + if err != nil { + return nil, err + } + c := &map[string]interface{}{} + if err := json.Unmarshal(contents, c); err != nil { + return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) + } + _, hasTerraformVersion := (*c)["terraform_version"] + + if !hasTerraformVersion { + return nil, fmt.Errorf("Input file is not Terraform Plan JSON: %v", i.Path) + } + + return &tfPlanLoader{ + path: i.Path, + content: c, + }, nil + }, +}) + +type tfPlanLoader struct { + path string + content *map[string]interface{} +} + +func (l *tfPlanLoader) RegulaInput() RegulaInput { + return RegulaInput{ + "filepath": l.path, + "content": l.content, + } +} diff --git a/pkg/loader/yaml.go b/pkg/loader/yaml.go deleted file mode 100644 index 3286de13..00000000 --- a/pkg/loader/yaml.go +++ /dev/null @@ -1,15 +0,0 @@ -package loader - -import "fmt" - -var YamlTypeDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { - if !opts.IgnoreExt && i.Ext != ".yaml" && i.Ext != ".yml" { - return nil, fmt.Errorf("File does not have .yaml or .yml extension: %v", i.Path) - } - - return i.DetectType(*CfnYamlDetector, DetectOptions{ - IgnoreExt: true, - }) - }, -}) From 35e9887f7a2a69ef4824b8f045eb60c07a55697b Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 15:01:17 -0400 Subject: [PATCH 22/65] [RM-5352] Better loader behavior --- pkg/loader/base.go | 15 ++++++-- pkg/loader/cfnjson.go | 12 +++++++ pkg/loader/cfnyaml.go | 12 +++++++ pkg/loader/loadpaths.go | 80 +++++++++++++++++++++++++++++++++-------- pkg/loader/tfplan.go | 12 +++++++ 5 files changed, 114 insertions(+), 17 deletions(-) diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 7ef462f9..e657658e 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -9,6 +9,8 @@ import ( git2go "github.com/libgit2/git2go/v31" ) +const StdIn = "" + type InputType int const ( @@ -25,10 +27,20 @@ var InputTypeIds = map[InputType][]string{ CfnYaml: {"cfn-yaml"}, } +type LoadedPaths interface { + AddLoader(loader Loader) + Location(path string, attributePath []string) (*Location, error) + AlreadyLoaded(path string) bool + RegulaInput() []RegulaInput + LoadedConfigurations() int +} + type RegulaInput map[string]interface{} type Loader interface { RegulaInput() RegulaInput + LoadedFiles() []string + Location(attributePath []string) (*Location, error) } type Location struct { @@ -38,7 +50,6 @@ type Location struct { } type LocationAwareLoader interface { - Location(attributePath string) (*Location, error) } type LoaderFactory func(inputPath InputPath) (Loader, error) @@ -188,7 +199,7 @@ func (i *InputFile) ReadContents() ([]byte, error) { return i.cachedContents, nil } - if i.Name == "-" { + if i.Name == StdIn { contents, err := io.ReadAll(os.Stdin) if err != nil { i.cachedContents = []byte{} diff --git a/pkg/loader/cfnjson.go b/pkg/loader/cfnjson.go index c4e9050d..4adcc775 100644 --- a/pkg/loader/cfnjson.go +++ b/pkg/loader/cfnjson.go @@ -44,3 +44,15 @@ func (l *cfnJsonLoader) RegulaInput() RegulaInput { "content": l.content, } } + +func (l *cfnJsonLoader) Location(attributePath []string) (*Location, error) { + return &Location{ + Path: l.path, + Line: 0, + Col: 0, + }, nil +} + +func (l *cfnJsonLoader) LoadedFiles() []string { + return []string{l.path} +} diff --git a/pkg/loader/cfnyaml.go b/pkg/loader/cfnyaml.go index 7d0d85d4..ceb6a7df 100644 --- a/pkg/loader/cfnyaml.go +++ b/pkg/loader/cfnyaml.go @@ -47,6 +47,18 @@ func (l *cfnYamlLoader) RegulaInput() RegulaInput { } } +func (l *cfnYamlLoader) Location(attributePath []string) (*Location, error) { + return &Location{ + Path: l.path, + Line: 0, + Col: 0, + }, nil +} + +func (l *cfnYamlLoader) LoadedFiles() []string { + return []string{l.path} +} + type cfnTemplate struct { Contents map[string]interface{} } diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 70358bac..bb3cf004 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -10,22 +10,53 @@ import ( ) type LoadedFiles struct { - Loaders map[string]Loader + loaders map[string]Loader + loadedFiles map[string]bool +} + +func NewLoadedFiles() *LoadedFiles { + return &LoadedFiles{ + loaders: map[string]Loader{}, + loadedFiles: map[string]bool{}, + } +} + +func (l *LoadedFiles) AddLoader(path string, loader Loader) { + l.loaders[path] = loader + for _, f := range loader.LoadedFiles() { + l.loadedFiles[f] = true + } } func (l *LoadedFiles) RegulaInput() []RegulaInput { keys := []string{} - for k := range l.Loaders { + for k := range l.loaders { keys = append(keys, k) } sort.Strings(keys) input := []RegulaInput{} for _, k := range keys { - input = append(input, l.Loaders[k].RegulaInput()) + input = append(input, l.loaders[k].RegulaInput()) } return input } +func (l *LoadedFiles) Location(path string, attributePath []string) (*Location, error) { + loader, ok := l.loaders[path] + if !ok { + return nil, fmt.Errorf("Unable to determine location for given path %v and attribute path %v", path, attributePath) + } + return loader.Location(attributePath) +} + +func (l *LoadedFiles) AlreadyLoaded(path string) bool { + return l.loadedFiles[path] +} + +func (l *LoadedFiles) LoadedConfigurations() int { + return len(l.loadedFiles) +} + type LoadPathsOptions struct { Paths []string InputType InputType @@ -33,27 +64,37 @@ type LoadPathsOptions struct { } func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { - loaders := map[string]Loader{} + loadedFiles := NewLoadedFiles() detector, err := detectorByInputType(options.InputType) if err != nil { return nil, err } walkFunc := func(i *InputPath) error { + inputPath := *i + if loadedFiles.AlreadyLoaded(inputPath.GetPath()) { + return nil + } // Ignore errors when we're recursing - loader, _ := (*i).DetectType(*detector, DetectOptions{ + loader, _ := inputPath.DetectType(*detector, DetectOptions{ IgnoreExt: false, }) if loader != nil { - loaders[(*i).GetPath()] = loader + loadedFiles.AddLoader(inputPath.GetPath(), loader) } return nil } gitRepoFinder := git.NewGitRepoFinder() for _, path := range options.Paths { if path == "-" { + path = StdIn + } + if loadedFiles.AlreadyLoaded(path) { + continue + } + if path == StdIn { i := InputFile{ - Path: "", - Name: "-", + Path: StdIn, + Name: StdIn, Ext: "", } loader, err := i.DetectType(*detector, DetectOptions{ @@ -63,7 +104,8 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { return nil, err } if loader != nil { - loaders[path] = loader + loadedFiles.AddLoader(StdIn, loader) + // loaders[path] = loader } continue } @@ -74,10 +116,20 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { } var i InputPath if info.IsDir() { + // We want to override the gitignore behavior if the user explicitly gives + // us a directory that is ignored. + noIgnore := options.NoIgnore + if !noIgnore { + repo := gitRepoFinder.FindRepo(path) + ignored, _ := repo.IsPathIgnored(path) + if ignored { + noIgnore = true + } + } i, err = NewInputDirectory(NewInputDirectoryOptions{ Path: path, Name: name, - NoIgnore: options.NoIgnore, + NoIgnore: noIgnore, GitRepoFinder: gitRepoFinder, }) if err != nil { @@ -93,19 +145,17 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { return nil, err } if loader != nil { - loaders[path] = loader + loadedFiles.AddLoader(path, loader) } if err := i.Walk(walkFunc); err != nil { return nil, err } } - if len(loaders) < 1 { + if loadedFiles.LoadedConfigurations() < 1 { return nil, fmt.Errorf("No loadable files in provided paths: %v", options.Paths) } - return &LoadedFiles{ - Loaders: loaders, - }, nil + return loadedFiles, nil } func detectorByInputType(inputType InputType) (*TypeDetector, error) { diff --git a/pkg/loader/tfplan.go b/pkg/loader/tfplan.go index d00b54c8..ea63b89d 100644 --- a/pkg/loader/tfplan.go +++ b/pkg/loader/tfplan.go @@ -42,3 +42,15 @@ func (l *tfPlanLoader) RegulaInput() RegulaInput { "content": l.content, } } + +func (l *tfPlanLoader) LoadedFiles() []string { + return []string{l.path} +} + +func (l *tfPlanLoader) Location(attributePath []string) (*Location, error) { + return &Location{ + Path: l.path, + Line: 0, + Col: 0, + }, nil +} From 0dfef2448a2a3b5757d7140e5a46da49c2fc847a Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 15:59:01 -0400 Subject: [PATCH 23/65] [RM-5352] Started fixing linting errors --- cmd/run.go | 2 +- cmd/version.go | 3 +- pkg/git/repo.go | 13 ++-- pkg/loader/base.go | 97 ++++++++++++++++--------- pkg/loader/cfnjson.go | 14 ++-- pkg/loader/cfnyaml.go | 14 ++-- pkg/loader/loadpaths.go | 134 +++++++++++++++++------------------ pkg/loader/tfplan.go | 4 +- pkg/reporter/base.go | 18 ++--- pkg/reporter/get-reporter.go | 4 +- pkg/reporter/json.go | 2 +- pkg/reporter/junit.go | 4 +- pkg/reporter/table.go | 4 +- pkg/reporter/tap.go | 8 +-- 14 files changed, 180 insertions(+), 141 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index 72106b2d..830cde3a 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -109,7 +109,7 @@ func NewRunCommand() *cobra.Command { cmd.Flags().BoolP("no-ignore", "n", false, "Disable .gitignore rules") cmd.Flags().BoolP("debug", "d", false, "Enable debug output") cmd.Flags().VarP( - enumflag.New(&inputType, "input-type", loader.InputTypeIds, enumflag.EnumCaseInsensitive), + enumflag.New(&inputType, "input-type", loader.InputTypeIDs, enumflag.EnumCaseInsensitive), "input-type", "t", "Explicitly set the input type") cmd.Flags().VarP( diff --git a/cmd/version.go b/cmd/version.go index e6f683a5..e1cc05e3 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,3 +1,5 @@ +package cmd + // Copyright 2021 Fugue, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,7 +13,6 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -package cmd // Default build-time variables. // These values are overridden via ldflags diff --git a/pkg/git/repo.go b/pkg/git/repo.go index 9f8404f4..a67a8100 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -7,17 +7,22 @@ import ( git "github.com/libgit2/git2go/v31" ) -type GitRepoFinder struct { +// RepoFinder finds the git repository for a given directory. +type RepoFinder struct { cache map[string]*git.Repository } -func NewGitRepoFinder() *GitRepoFinder { - return &GitRepoFinder{ +// NewRepoFinder returns a new RepoFinder instance +func NewRepoFinder() *RepoFinder { + return &RepoFinder{ cache: map[string]*git.Repository{}, } } -func (s *GitRepoFinder) FindRepo(path string) *git.Repository { +// FindRepo takes a directory path and finds the git repository for it if one exists. +// It works by searching within the given directory, followed by searching in parent +// directories until it either reaches the top-level directory or encounters an error. +func (s *RepoFinder) FindRepo(path string) *git.Repository { absPath, err := filepath.Abs(path) if err != nil { return nil diff --git a/pkg/loader/base.go b/pkg/loader/base.go index e657658e..42c97721 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -9,88 +9,120 @@ import ( git2go "github.com/libgit2/git2go/v31" ) +// StdIn is the path used for stdin. const StdIn = "" +// InputType is a flag that determines which types regula should look for. type InputType int const ( + // Auto means that regula will automatically try to determine which input types are + // in the given paths. Auto InputType = iota + // TfPlan means that regula will only look for Terraform plan JSON files in given + // directories and it will assume that given files are Terraform plan JSON. TfPlan - CfnJson - CfnYaml + // CfnJSON means that regula will only look for CloudFormation JSON files in given + // directories and it will assume that given files are CloudFormation JSON. + CfnJSON + // CfnYAML means that regula will only look for CloudFormation YAML files in given + // directories and it will assume that given files are CloudFormation YAML. + CfnYAML ) -var InputTypeIds = map[InputType][]string{ +// InputTypeIDs maps the InputType enums to string values that can be specified in +// CLI options. +var InputTypeIDs = map[InputType][]string{ Auto: {"auto"}, TfPlan: {"tf-plan"}, - CfnJson: {"cfn-json"}, - CfnYaml: {"cfn-yaml"}, + CfnJSON: {"cfn-json"}, + CfnYAML: {"cfn-yaml"}, } -type LoadedPaths interface { - AddLoader(loader Loader) +// LoadedConfigurations is a container for IACConfigurations loaded by Regula. +type LoadedConfigurations interface { + // AddConfiguration adds a configuration entry for the given path + AddConfiguration(path string, config IACConfiguration) + // Location resolves a file path and attribute path from the regula output to a + // location within a file. Location(path string, attributePath []string) (*Location, error) + // AlreadyLoaded indicates whether the given path has already been loaded as part + // of another IACConfiguration. AlreadyLoaded(path string) bool + // RegulaInput renders the RegulaInput from all of the contained configurations. RegulaInput() []RegulaInput - LoadedConfigurations() int + // Count returns the number of loaded configurations. + Count() int } +// RegulaInput is a generic map that can be fed to OPA for regula. type RegulaInput map[string]interface{} -type Loader interface { +// IACConfiguration is a loaded IaC Configuration. +type IACConfiguration interface { + // RegulaInput returns a input for regula. RegulaInput() RegulaInput + // LoadedFiles are all of the files contained within this configuration. LoadedFiles() []string + // Location resolves an attribute path to to a file, line and column. Location(attributePath []string) (*Location, error) } +// Location is a filepath, line and column. type Location struct { Path string Line int Col int } -type LocationAwareLoader interface { -} - -type LoaderFactory func(inputPath InputPath) (Loader, error) - +// DetectOptions are options passed to the configuration detectors. type DetectOptions struct { IgnoreExt bool } -type TypeDetector struct { - DetectDirectory func(i *InputDirectory, opts DetectOptions) (Loader, error) - DetectFile func(i *InputFile, opts DetectOptions) (Loader, error) +// ConfigurationDetector implements the visitor part of the visitor pattern for the +// concrete InputPath implementations. A ConfigurationDetector implementation must +// contain functions to visit both directories and files. +type ConfigurationDetector struct { + DetectDirectory func(i *InputDirectory, opts DetectOptions) (IACConfiguration, error) + DetectFile func(i *InputFile, opts DetectOptions) (IACConfiguration, error) } -func NewTypeDetector(t *TypeDetector) *TypeDetector { +// NewConfigurationDetector is a convenience function that will fill in empty detection +// functions for a configuration detector. +func NewConfigurationDetector(t *ConfigurationDetector) *ConfigurationDetector { if t.DetectDirectory == nil { - t.DetectDirectory = func(i *InputDirectory, opts DetectOptions) (Loader, error) { + t.DetectDirectory = func(i *InputDirectory, opts DetectOptions) (IACConfiguration, error) { return nil, nil } } if t.DetectFile == nil { - t.DetectFile = func(i *InputFile, opts DetectOptions) (Loader, error) { + t.DetectFile = func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { return nil, nil } } return t } +// InputPath is a generic interface to represent both directories and files that +// can serve as inputs for a ConfigurationDetector. type InputPath interface { - DetectType(d TypeDetector, opts DetectOptions) (Loader, error) + DetectType(d ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) Children() []*InputPath IsDir() bool Walk(w func(i *InputPath) error) error GetPath() string } + +// InputDirectory is a concrete implementation of the InputPath interface that +// represents a directory. type InputDirectory struct { Path string Name string Contents []*InputPath } -func (i *InputDirectory) DetectType(d TypeDetector, opts DetectOptions) (Loader, error) { +func (i *InputDirectory) DetectType(d ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) { return d.DetectDirectory(i, opts) } @@ -118,11 +150,12 @@ func (i InputDirectory) GetPath() string { return i.Path } +// NewInputDirectoryOptions contains options for instantiating a new InputDirectory type NewInputDirectoryOptions struct { Path string Name string NoIgnore bool - GitRepoFinder *git.GitRepoFinder + GitRepoFinder *git.RepoFinder } func NewInputDirectory(opts NewInputDirectoryOptions) (*InputDirectory, error) { @@ -174,7 +207,7 @@ type InputFile struct { cachedContents []byte } -func (i *InputFile) DetectType(d TypeDetector, opts DetectOptions) (Loader, error) { +func (i *InputFile) DetectType(d ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) { return d.DetectFile(i, opts) } @@ -207,15 +240,15 @@ func (i *InputFile) ReadContents() ([]byte, error) { } i.cachedContents = contents return contents, nil - } else { - contents, err := os.ReadFile(i.Path) - if err != nil { - i.cachedContents = []byte{} - return nil, err - } - i.cachedContents = contents - return contents, nil } + + contents, err := os.ReadFile(i.Path) + if err != nil { + i.cachedContents = []byte{} + return nil, err + } + i.cachedContents = contents + return contents, nil } func NewInputFile(path string, name string) *InputFile { diff --git a/pkg/loader/cfnjson.go b/pkg/loader/cfnjson.go index 4adcc775..8b1b0b08 100644 --- a/pkg/loader/cfnjson.go +++ b/pkg/loader/cfnjson.go @@ -5,8 +5,8 @@ import ( "fmt" ) -var CfnJsonDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { +var CfnJSONDetector = NewConfigurationDetector(&ConfigurationDetector{ + DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { if !opts.IgnoreExt && i.Ext != ".json" { return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) } @@ -26,26 +26,26 @@ var CfnJsonDetector = NewTypeDetector(&TypeDetector{ return nil, fmt.Errorf("Input file is not CloudFormation JSON: %v", i.Path) } - return &cfnJsonLoader{ + return &cfnJSON{ path: i.Path, content: c, }, nil }, }) -type cfnJsonLoader struct { +type cfnJSON struct { path string content *map[string]interface{} } -func (l *cfnJsonLoader) RegulaInput() RegulaInput { +func (l *cfnJSON) RegulaInput() RegulaInput { return RegulaInput{ "filepath": l.path, "content": l.content, } } -func (l *cfnJsonLoader) Location(attributePath []string) (*Location, error) { +func (l *cfnJSON) Location(attributePath []string) (*Location, error) { return &Location{ Path: l.path, Line: 0, @@ -53,6 +53,6 @@ func (l *cfnJsonLoader) Location(attributePath []string) (*Location, error) { }, nil } -func (l *cfnJsonLoader) LoadedFiles() []string { +func (l *cfnJSON) LoadedFiles() []string { return []string{l.path} } diff --git a/pkg/loader/cfnyaml.go b/pkg/loader/cfnyaml.go index ceb6a7df..6b6d825b 100644 --- a/pkg/loader/cfnyaml.go +++ b/pkg/loader/cfnyaml.go @@ -7,8 +7,8 @@ import ( "gopkg.in/yaml.v3" ) -var CfnYamlDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { +var CfnYAMLDetector = NewConfigurationDetector(&ConfigurationDetector{ + DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { if !opts.IgnoreExt && i.Ext != ".yaml" && i.Ext != ".yml" { return nil, fmt.Errorf("File does not have .yaml or .yml extension: %v", i.Path) } @@ -28,26 +28,26 @@ var CfnYamlDetector = NewTypeDetector(&TypeDetector{ return nil, fmt.Errorf("Input file is not CloudFormation YAML: %v", i.Path) } - return &cfnYamlLoader{ + return &cfnYAML{ path: i.Path, template: *template, }, nil }, }) -type cfnYamlLoader struct { +type cfnYAML struct { path string template cfnTemplate } -func (l *cfnYamlLoader) RegulaInput() RegulaInput { +func (l *cfnYAML) RegulaInput() RegulaInput { return RegulaInput{ "filepath": l.path, "content": l.template.Contents, } } -func (l *cfnYamlLoader) Location(attributePath []string) (*Location, error) { +func (l *cfnYAML) Location(attributePath []string) (*Location, error) { return &Location{ Path: l.path, Line: 0, @@ -55,7 +55,7 @@ func (l *cfnYamlLoader) Location(attributePath []string) (*Location, error) { }, nil } -func (l *cfnYamlLoader) LoadedFiles() []string { +func (l *cfnYAML) LoadedFiles() []string { return []string{l.path} } diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index bb3cf004..093dbcb2 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -9,69 +9,21 @@ import ( "github.com/fugue/regula/pkg/git" ) -type LoadedFiles struct { - loaders map[string]Loader - loadedFiles map[string]bool -} - -func NewLoadedFiles() *LoadedFiles { - return &LoadedFiles{ - loaders: map[string]Loader{}, - loadedFiles: map[string]bool{}, - } -} - -func (l *LoadedFiles) AddLoader(path string, loader Loader) { - l.loaders[path] = loader - for _, f := range loader.LoadedFiles() { - l.loadedFiles[f] = true - } -} - -func (l *LoadedFiles) RegulaInput() []RegulaInput { - keys := []string{} - for k := range l.loaders { - keys = append(keys, k) - } - sort.Strings(keys) - input := []RegulaInput{} - for _, k := range keys { - input = append(input, l.loaders[k].RegulaInput()) - } - return input -} - -func (l *LoadedFiles) Location(path string, attributePath []string) (*Location, error) { - loader, ok := l.loaders[path] - if !ok { - return nil, fmt.Errorf("Unable to determine location for given path %v and attribute path %v", path, attributePath) - } - return loader.Location(attributePath) -} - -func (l *LoadedFiles) AlreadyLoaded(path string) bool { - return l.loadedFiles[path] -} - -func (l *LoadedFiles) LoadedConfigurations() int { - return len(l.loadedFiles) -} - type LoadPathsOptions struct { Paths []string InputType InputType NoIgnore bool } -func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { - loadedFiles := NewLoadedFiles() +func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { + configurations := newLoadedConfigurations() detector, err := detectorByInputType(options.InputType) if err != nil { return nil, err } walkFunc := func(i *InputPath) error { inputPath := *i - if loadedFiles.AlreadyLoaded(inputPath.GetPath()) { + if configurations.AlreadyLoaded(inputPath.GetPath()) { return nil } // Ignore errors when we're recursing @@ -79,16 +31,16 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { IgnoreExt: false, }) if loader != nil { - loadedFiles.AddLoader(inputPath.GetPath(), loader) + configurations.AddConfiguration(inputPath.GetPath(), loader) } return nil } - gitRepoFinder := git.NewGitRepoFinder() + gitRepoFinder := git.NewRepoFinder() for _, path := range options.Paths { if path == "-" { path = StdIn } - if loadedFiles.AlreadyLoaded(path) { + if configurations.AlreadyLoaded(path) { continue } if path == StdIn { @@ -104,7 +56,7 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { return nil, err } if loader != nil { - loadedFiles.AddLoader(StdIn, loader) + configurations.AddConfiguration(StdIn, loader) // loaders[path] = loader } continue @@ -145,27 +97,75 @@ func LoadPaths(options LoadPathsOptions) (*LoadedFiles, error) { return nil, err } if loader != nil { - loadedFiles.AddLoader(path, loader) + configurations.AddConfiguration(path, loader) } if err := i.Walk(walkFunc); err != nil { return nil, err } } - if loadedFiles.LoadedConfigurations() < 1 { + if configurations.Count() < 1 { return nil, fmt.Errorf("No loadable files in provided paths: %v", options.Paths) } - return loadedFiles, nil + return configurations, nil +} + +type loadedConfigurations struct { + configurations map[string]IACConfiguration + loadedPaths map[string]bool +} + +func newLoadedConfigurations() *loadedConfigurations { + return &loadedConfigurations{ + configurations: map[string]IACConfiguration{}, + loadedPaths: map[string]bool{}, + } +} + +func (l *loadedConfigurations) AddConfiguration(path string, config IACConfiguration) { + l.configurations[path] = config + for _, f := range config.LoadedFiles() { + l.loadedPaths[f] = true + } +} + +func (l *loadedConfigurations) RegulaInput() []RegulaInput { + keys := []string{} + for k := range l.configurations { + keys = append(keys, k) + } + sort.Strings(keys) + input := []RegulaInput{} + for _, k := range keys { + input = append(input, l.configurations[k].RegulaInput()) + } + return input +} + +func (l *loadedConfigurations) Location(path string, attributePath []string) (*Location, error) { + loader, ok := l.configurations[path] + if !ok { + return nil, fmt.Errorf("Unable to determine location for given path %v and attribute path %v", path, attributePath) + } + return loader.Location(attributePath) +} + +func (l *loadedConfigurations) AlreadyLoaded(path string) bool { + return l.loadedPaths[path] +} + +func (l *loadedConfigurations) Count() int { + return len(l.configurations) } -func detectorByInputType(inputType InputType) (*TypeDetector, error) { +func detectorByInputType(inputType InputType) (*ConfigurationDetector, error) { switch inputType { case Auto: return AutoDetector, nil - case CfnYaml: - return CfnYamlDetector, nil - case CfnJson: - return CfnJsonDetector, nil + case CfnYAML: + return CfnYAMLDetector, nil + case CfnJSON: + return CfnJSONDetector, nil case TfPlan: return TfPlanDetector, nil default: @@ -173,17 +173,17 @@ func detectorByInputType(inputType InputType) (*TypeDetector, error) { } } -var AutoDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { +var AutoDetector = NewConfigurationDetector(&ConfigurationDetector{ + DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { l, err := i.DetectType(*TfPlanDetector, opts) if err == nil { return l, nil } - l, err = i.DetectType(*CfnJsonDetector, opts) + l, err = i.DetectType(*CfnJSONDetector, opts) if err == nil { return l, nil } - l, err = i.DetectType(*CfnYamlDetector, opts) + l, err = i.DetectType(*CfnYAMLDetector, opts) if err == nil { return l, nil } diff --git a/pkg/loader/tfplan.go b/pkg/loader/tfplan.go index ea63b89d..9bb9b013 100644 --- a/pkg/loader/tfplan.go +++ b/pkg/loader/tfplan.go @@ -5,8 +5,8 @@ import ( "fmt" ) -var TfPlanDetector = NewTypeDetector(&TypeDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (Loader, error) { +var TfPlanDetector = NewConfigurationDetector(&ConfigurationDetector{ + DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { if !opts.IgnoreExt && i.Ext != ".json" { return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) } diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index 0eb91d9a..adb8e960 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -33,7 +33,7 @@ var SeverityIds = map[Severity][]string{ type Format int const ( - Json Format = iota + JSON Format = iota Table Junit Tap @@ -42,7 +42,7 @@ const ( ) var FormatIds = map[Format][]string{ - Json: {"json"}, + JSON: {"json"}, Table: {"table"}, Junit: {"junit"}, Tap: {"tap"}, @@ -86,7 +86,7 @@ func (o RegulaOutput) ExceedsSeverity(severity Severity) bool { } type ResourceResults struct { - ResourceId string + ResourceID string ResourceType string Results []RuleResult Pass bool @@ -129,10 +129,10 @@ func (o RegulaOutput) AggregateByFilepath() ResultsByFilepath { Pass: !r.IsFail(), } } - resourceResults, ok := filepathResults.Results[r.ResourceId] + resourceResults, ok := filepathResults.Results[r.ResourceID] if !ok { resourceResults = ResourceResults{ - ResourceId: r.ResourceId, + ResourceID: r.ResourceID, ResourceType: r.ResourceType, Results: []RuleResult{}, Pass: !r.IsFail(), @@ -140,7 +140,7 @@ func (o RegulaOutput) AggregateByFilepath() ResultsByFilepath { } resourceResults.Results = append(resourceResults.Results, r) resourceResults.Pass = resourceResults.Pass && !r.IsFail() - filepathResults.Results[r.ResourceId] = resourceResults + filepathResults.Results[r.ResourceID] = resourceResults filepathResults.Pass = filepathResults.Pass && resourceResults.Pass byFilepath[r.Filepath] = filepathResults } @@ -152,10 +152,10 @@ type RuleResult struct { Filepath string `json:"filepath"` Platform string `json:"platform"` Provider string `json:"provider"` - ResourceId string `json:"resource_id"` + ResourceID string `json:"resource_id"` ResourceType string `json:"resource_type"` RuleDescription string `json:"rule_description"` - RuleId string `json:"rule_id"` + RuleID string `json:"rule_id"` RuleMessage string `json:"rule_message"` RuleName string `json:"rule_name"` RuleResult string `json:"rule_result"` @@ -191,7 +191,7 @@ type Summary struct { Severities map[string]int `json:"severities"` } -func ParseRegulaOutput(_ *loader.LoadedFiles, r rego.Result) (*RegulaOutput, error) { +func ParseRegulaOutput(_ loader.LoadedConfigurations, r rego.Result) (*RegulaOutput, error) { j, err := json.Marshal(r.Expressions[0].Value) if err != nil { return nil, err diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/get-reporter.go index 4261055e..74f2a7cc 100644 --- a/pkg/reporter/get-reporter.go +++ b/pkg/reporter/get-reporter.go @@ -4,8 +4,8 @@ import "fmt" func GetReporter(format Format) (Reporter, error) { switch format { - case Json: - return JsonReporter, nil + case JSON: + return JSONReporter, nil case Table: return TableReporter, nil case Junit: diff --git a/pkg/reporter/json.go b/pkg/reporter/json.go index d58c8c0a..ecf415a6 100644 --- a/pkg/reporter/json.go +++ b/pkg/reporter/json.go @@ -5,7 +5,7 @@ import ( "encoding/json" ) -func JsonReporter(r *RegulaOutput) (string, error) { +func JSONReporter(r *RegulaOutput) (string, error) { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetEscapeHTML(false) diff --git a/pkg/reporter/junit.go b/pkg/reporter/junit.go index 6f9536d7..391aaad7 100644 --- a/pkg/reporter/junit.go +++ b/pkg/reporter/junit.go @@ -36,7 +36,7 @@ func (r ResourceResults) ToTestCase() JUnitTestCase { } } testCase := JUnitTestCase{ - Name: r.ResourceId, + Name: r.ResourceID, ClassName: r.ResourceType, Assertions: len(r.Results), } @@ -75,7 +75,7 @@ func (r ResultsByFilepath) ToTestSuites() JUnitTestSuites { func failureMessage(r RuleResult) string { return fmt.Sprintf( "Rule ID: %v\nRule Name: %v\nSeverity: %v\nMessage: %v", - r.RuleId, + r.RuleID, r.RuleName, r.RuleSeverity, r.Message(), diff --git a/pkg/reporter/table.go b/pkg/reporter/table.go index 0c44834f..1360d3ec 100644 --- a/pkg/reporter/table.go +++ b/pkg/reporter/table.go @@ -21,11 +21,11 @@ func TableReporter(o *RegulaOutput) (string, error) { message = r.RuleSummary } tableRow := TableRow{ - Resource: r.ResourceId, + Resource: r.ResourceID, Type: r.ResourceType, Filepath: r.Filepath, Severity: colorizeSeverity(r), - RuleID: r.RuleId, + RuleID: r.RuleID, RuleName: r.RuleName, Message: message, Result: colorizeResult(r.RuleResult), diff --git a/pkg/reporter/tap.go b/pkg/reporter/tap.go index eb61ae34..a24da24e 100644 --- a/pkg/reporter/tap.go +++ b/pkg/reporter/tap.go @@ -9,10 +9,10 @@ import ( func TapReporter(o *RegulaOutput) (string, error) { results := o.RuleResults sort.SliceStable(results, func(i, j int) bool { - if results[i].ResourceId == results[j].ResourceId { - return results[i].RuleId < results[j].RuleId + if results[i].ResourceID == results[j].ResourceID { + return results[i].RuleID < results[j].RuleID } - return results[i].ResourceId < results[j].ResourceId + return results[i].ResourceID < results[j].ResourceID }) tapOutput := []string{} @@ -50,6 +50,6 @@ func (r RuleResult) ToTapRow(idx int) TapRow { Index: idx, Message: r.Message(), Directive: directive, - Resource: r.ResourceId, + Resource: r.ResourceID, } } From 3262a014a91c921006726c976256c8ac72f861f2 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 16:36:11 -0400 Subject: [PATCH 24/65] [RM-5352] Add license and some inline docs --- Makefile | 2 +- cmd/root.go | 14 +++++++++++++ cmd/run.go | 39 ++++++++++++++++++++++-------------- cmd/run.txt | 32 +++++++++++++++++++++++++++++ cmd/version.go | 4 ++-- go.mod | 1 - go.sum | 3 --- pkg/git/repo.go | 14 +++++++++++++ pkg/loader/base.go | 14 +++++++++++++ pkg/loader/cfnjson.go | 14 +++++++++++++ pkg/loader/cfnyaml.go | 14 +++++++++++++ pkg/loader/loadpaths.go | 14 +++++++++++++ pkg/loader/tfplan.go | 14 +++++++++++++ pkg/rego/runner.go | 14 +++++++++++++ pkg/reporter/base.go | 14 +++++++++++++ pkg/reporter/get-reporter.go | 14 +++++++++++++ pkg/reporter/json.go | 14 +++++++++++++ pkg/reporter/junit.go | 14 +++++++++++++ pkg/reporter/table.go | 14 +++++++++++++ pkg/reporter/tap.go | 14 +++++++++++++ 20 files changed, 255 insertions(+), 22 deletions(-) create mode 100644 cmd/run.txt diff --git a/Makefile b/Makefile index acdac01f..7dd95ba3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BINARY = regula INSTALLED_BINARY = /usr/local/bin/$(BINARY) -CLI_SOURCE = $(shell find cmd pkg -type f -name '*.go') +CLI_SOURCE = $(shell find cmd pkg -type f -name '*.go') $(wildcard cmd/*.txt) go.mod go.sum REGO_LIB_SOURCE = $(shell find rego/lib -type f -name '*.rego') REGO_RULES_SOURCE = $(shell find rego/rules -type f -name '*.rego') GO = GO111MODULE=on go diff --git a/cmd/root.go b/cmd/root.go index f293d576..f44bb064 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package cmd import ( diff --git a/cmd/run.go b/cmd/run.go index 830cde3a..21d266fd 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,26 +1,44 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package cmd import ( "context" + _ "embed" "fmt" "os" "github.com/fugue/regula/pkg/loader" "github.com/fugue/regula/pkg/rego" "github.com/fugue/regula/pkg/reporter" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/thediveo/enumflag" "golang.org/x/crypto/ssh/terminal" ) +//go:embed run.txt +var longDescription string + func NewRunCommand() *cobra.Command { var inputType loader.InputType var format reporter.Format severity := reporter.Unknown cmd := &cobra.Command{ - Use: "run", - Short: "Run Regula rules on inputs", + Use: "run [input...]", + Short: "Evaluate rules against infrastructure-as-code with Regula.", + Long: longDescription, Run: func(cmd *cobra.Command, paths []string) { includes, err := cmd.Flags().GetStringSlice("include") if err != nil { @@ -37,14 +55,6 @@ func NewRunCommand() *cobra.Command { fmt.Println(err) os.Exit(1) } - debug, err := cmd.Flags().GetBool("debug") - if err != nil { - fmt.Println(err) - os.Exit(1) - } - if debug { - log.SetLevel(log.DebugLevel) - } ctx := context.TODO() ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ Ctx: ctx, @@ -104,14 +114,13 @@ func NewRunCommand() *cobra.Command { }, } - cmd.Flags().StringSliceP("include", "i", nil, "Select rego libraries to include") + cmd.Flags().StringSliceP("include", "i", nil, "Specify additional rego files or directories to include") cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") - cmd.Flags().BoolP("no-ignore", "n", false, "Disable .gitignore rules") - cmd.Flags().BoolP("debug", "d", false, "Enable debug output") + cmd.Flags().BoolP("no-ignore", "n", false, "Disable use of .gitignore") cmd.Flags().VarP( enumflag.New(&inputType, "input-type", loader.InputTypeIDs, enumflag.EnumCaseInsensitive), "input-type", "t", - "Explicitly set the input type") + "Set the input type for the given paths") cmd.Flags().VarP( enumflag.New(&severity, "severity", reporter.SeverityIds, enumflag.EnumCaseInsensitive), "severity", "s", diff --git a/cmd/run.txt b/cmd/run.txt new file mode 100644 index 00000000..375b7f00 --- /dev/null +++ b/cmd/run.txt @@ -0,0 +1,32 @@ +Evaluate rules against infrastructure-as-code contained in one or more paths. When run +without any paths, Regula will recursively search for IaC configurations within the +working directory. When a directory is given Regula will recursively search for IaC +configurations within that directory. When a file is given, Regula will assume that the +file contains an IaC configuration. If an input type is set, Regula will only search +for configurations of that type in the specified directories and it will assume that +specified files are of that input type. + +By default, Regula will exclude paths based on the patterns in the .gitignore file for +a specified directory. This behavior can be disabled with the --no-ignore option. + +Input types: + auto Automatically determine input types (default) + tf-plan Terraform plan JSON + cfn-json CloudFormation JSON + cfn-yaml CloudFormation YAML + +Output formats: + json A JSON report containing rule results and a summary (default) + table An ASCII table of rule results + junit The JUnit XML format + tap The Test Anything Protocol format + none Do not print any output on stdout + +Severities: + unknown Lowest setting. Used for rules without a severity specified (default) + informational + low + medium + high + critical + off Never exit with a non-zero exit code. diff --git a/cmd/version.go b/cmd/version.go index e1cc05e3..8bb4dd96 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,5 +1,3 @@ -package cmd - // Copyright 2021 Fugue, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,6 +12,8 @@ package cmd // See the License for the specific language governing permissions and // limitations under the License. +package cmd + // Default build-time variables. // These values are overridden via ldflags var ( diff --git a/go.mod b/go.mod index 746e05b9..2b73474c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,6 @@ require ( github.com/libgit2/git2go/v31 v31.4.14 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 - github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v1.1.3 github.com/thediveo/enumflag v0.10.1 golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c diff --git a/go.sum b/go.sum index e598dc8e..b64ca56d 100644 --- a/go.sum +++ b/go.sum @@ -67,7 +67,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -277,7 +276,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -349,7 +347,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/thediveo/enumflag v0.10.1 h1:DB3Ag69VZ7BCv6jzKECrZ0ebZrHLzFRMIFYt96s4OxM= diff --git a/pkg/git/repo.go b/pkg/git/repo.go index a67a8100..a411a90e 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package git import ( diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 42c97721..6c007685 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package loader import ( diff --git a/pkg/loader/cfnjson.go b/pkg/loader/cfnjson.go index 8b1b0b08..6ed2287e 100644 --- a/pkg/loader/cfnjson.go +++ b/pkg/loader/cfnjson.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package loader import ( diff --git a/pkg/loader/cfnyaml.go b/pkg/loader/cfnyaml.go index 6b6d825b..bfb235cd 100644 --- a/pkg/loader/cfnyaml.go +++ b/pkg/loader/cfnyaml.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package loader import ( diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 093dbcb2..1f62a1bb 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package loader import ( diff --git a/pkg/loader/tfplan.go b/pkg/loader/tfplan.go index 9bb9b013..f9751403 100644 --- a/pkg/loader/tfplan.go +++ b/pkg/loader/tfplan.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package loader import ( diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go index f3f70110..818e7813 100644 --- a/pkg/rego/runner.go +++ b/pkg/rego/runner.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package rego import ( diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index adb8e960..93c3cd7f 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package reporter import ( diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/get-reporter.go index 74f2a7cc..fbc3d237 100644 --- a/pkg/reporter/get-reporter.go +++ b/pkg/reporter/get-reporter.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package reporter import "fmt" diff --git a/pkg/reporter/json.go b/pkg/reporter/json.go index ecf415a6..76ff7c0b 100644 --- a/pkg/reporter/json.go +++ b/pkg/reporter/json.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package reporter import ( diff --git a/pkg/reporter/junit.go b/pkg/reporter/junit.go index 391aaad7..6b8ee8b7 100644 --- a/pkg/reporter/junit.go +++ b/pkg/reporter/junit.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package reporter import ( diff --git a/pkg/reporter/table.go b/pkg/reporter/table.go index 1360d3ec..61bb7710 100644 --- a/pkg/reporter/table.go +++ b/pkg/reporter/table.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package reporter import ( diff --git a/pkg/reporter/tap.go b/pkg/reporter/tap.go index a24da24e..40fb2dc0 100644 --- a/pkg/reporter/tap.go +++ b/pkg/reporter/tap.go @@ -1,3 +1,17 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package reporter import ( From 2b0781e10b35edc8d82ed90e8ac9637f36004b14 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 28 Apr 2021 16:43:50 -0400 Subject: [PATCH 25/65] [RM-5352] Simplify lines that set noIgnore --- pkg/loader/loadpaths.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 1f62a1bb..fc1ba645 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -88,9 +88,7 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { if !noIgnore { repo := gitRepoFinder.FindRepo(path) ignored, _ := repo.IsPathIgnored(path) - if ignored { - noIgnore = true - } + noIgnore = ignored } i, err = NewInputDirectory(NewInputDirectoryOptions{ Path: path, From 07d929a9fd0f05a804c626417eb026fb24fdff6b Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 29 Apr 2021 12:09:39 -0400 Subject: [PATCH 26/65] [RM-5352] Better interfaces --- cmd/run.txt | 3 +- pkg/git/repo.go | 38 +++++- pkg/loader/auto.go | 33 +++++ pkg/loader/base.go | 203 +++--------------------------- pkg/loader/{cfnyaml.go => cfn.go} | 66 +++++----- pkg/loader/cfnjson.go | 72 ----------- pkg/loader/input.go | 163 ++++++++++++++++++++++++ pkg/loader/loadpaths.go | 101 +++++++-------- pkg/loader/tfplan.go | 53 ++++---- 9 files changed, 361 insertions(+), 371 deletions(-) create mode 100644 pkg/loader/auto.go rename pkg/loader/{cfnyaml.go => cfn.go} (76%) delete mode 100644 pkg/loader/cfnjson.go create mode 100644 pkg/loader/input.go diff --git a/cmd/run.txt b/cmd/run.txt index 375b7f00..1195c71d 100644 --- a/cmd/run.txt +++ b/cmd/run.txt @@ -12,8 +12,7 @@ a specified directory. This behavior can be disabled with the --no-ignore option Input types: auto Automatically determine input types (default) tf-plan Terraform plan JSON - cfn-json CloudFormation JSON - cfn-yaml CloudFormation YAML + cfn CloudFormation template in YAML or JSON format Output formats: json A JSON report containing rule results and a summary (default) diff --git a/pkg/git/repo.go b/pkg/git/repo.go index a411a90e..4850812d 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -21,22 +21,46 @@ import ( git "github.com/libgit2/git2go/v31" ) +type Repo interface { + IsPathIgnored(path string) bool +} + +type repo struct { + path string + repo *git.Repository +} + +func (r *repo) IsPathIgnored(path string) bool { + absPath, err := filepath.Abs(path) + if err != nil { + return false + } + // git2go's IsPathIgnored results differ from git check-ignore if you've got a + // pattern like we do where the path matches the name of the directory. It's + // safe to assume that this should always be false. + if path == r.path { + return false + } + ignored, _ := r.repo.IsPathIgnored(absPath) + return ignored +} + // RepoFinder finds the git repository for a given directory. type RepoFinder struct { - cache map[string]*git.Repository + cache map[string]Repo } // NewRepoFinder returns a new RepoFinder instance func NewRepoFinder() *RepoFinder { return &RepoFinder{ - cache: map[string]*git.Repository{}, + cache: map[string]Repo{}, } } // FindRepo takes a directory path and finds the git repository for it if one exists. // It works by searching within the given directory, followed by searching in parent // directories until it either reaches the top-level directory or encounters an error. -func (s *RepoFinder) FindRepo(path string) *git.Repository { +func (s *RepoFinder) FindRepo(path string) Repo { absPath, err := filepath.Abs(path) if err != nil { return nil @@ -56,13 +80,15 @@ func (s *RepoFinder) FindRepo(path string) *git.Repository { } for _, e := range entries { if e.Name() == ".git" { - repo, err := git.OpenRepository(filepath.Join(absPath, e.Name())) + r, err := git.OpenRepository(filepath.Join(absPath, e.Name())) if err != nil { s.cache[absPath] = nil return nil } - s.cache[absPath] = repo - return repo + s.cache[absPath] = &repo{ + repo: r, + } + return s.cache[absPath] } } traversedPaths = append(traversedPaths, absPath) diff --git a/pkg/loader/auto.go b/pkg/loader/auto.go new file mode 100644 index 00000000..8bc92968 --- /dev/null +++ b/pkg/loader/auto.go @@ -0,0 +1,33 @@ +package loader + +type AutoDetector struct { + detectors []ConfigurationDetector +} + +func (a *AutoDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) { + for _, d := range a.detectors { + l, err := i.DetectType(d, opts) + if err == nil && l != nil { + return l, nil + } + } + + return nil, nil +} + +func (a *AutoDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) { + for _, d := range a.detectors { + l, err := i.DetectType(d, opts) + if err == nil && l != nil { + return l, nil + } + } + + return nil, nil +} + +func NewAutoDetector(detectors ...ConfigurationDetector) *AutoDetector { + return &AutoDetector{ + detectors: detectors, + } +} diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 6c007685..d0f84789 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -14,15 +14,6 @@ package loader -import ( - "io" - "os" - "path/filepath" - - "github.com/fugue/regula/pkg/git" - git2go "github.com/libgit2/git2go/v31" -) - // StdIn is the path used for stdin. const StdIn = "" @@ -36,21 +27,17 @@ const ( // TfPlan means that regula will only look for Terraform plan JSON files in given // directories and it will assume that given files are Terraform plan JSON. TfPlan - // CfnJSON means that regula will only look for CloudFormation JSON files in given + // Cfn means that regula will only look for CloudFormation template files in given // directories and it will assume that given files are CloudFormation JSON. - CfnJSON - // CfnYAML means that regula will only look for CloudFormation YAML files in given - // directories and it will assume that given files are CloudFormation YAML. - CfnYAML + Cfn ) // InputTypeIDs maps the InputType enums to string values that can be specified in // CLI options. var InputTypeIDs = map[InputType][]string{ - Auto: {"auto"}, - TfPlan: {"tf-plan"}, - CfnJSON: {"cfn-json"}, - CfnYAML: {"cfn-yaml"}, + Auto: {"auto"}, + TfPlan: {"tf-plan"}, + Cfn: {"cfn"}, } // LoadedConfigurations is a container for IACConfigurations loaded by Regula. @@ -96,180 +83,30 @@ type DetectOptions struct { // ConfigurationDetector implements the visitor part of the visitor pattern for the // concrete InputPath implementations. A ConfigurationDetector implementation must -// contain functions to visit both directories and files. -type ConfigurationDetector struct { - DetectDirectory func(i *InputDirectory, opts DetectOptions) (IACConfiguration, error) - DetectFile func(i *InputFile, opts DetectOptions) (IACConfiguration, error) -} - -// NewConfigurationDetector is a convenience function that will fill in empty detection -// functions for a configuration detector. -func NewConfigurationDetector(t *ConfigurationDetector) *ConfigurationDetector { - if t.DetectDirectory == nil { - t.DetectDirectory = func(i *InputDirectory, opts DetectOptions) (IACConfiguration, error) { - return nil, nil - } - } - if t.DetectFile == nil { - t.DetectFile = func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { - return nil, nil - } - } - return t +// contain functions to visit both directories and files. An empty implementation +// must return nil, nil to indicate that the InputPath has been ignored. +type ConfigurationDetector interface { + DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) + DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) } // InputPath is a generic interface to represent both directories and files that // can serve as inputs for a ConfigurationDetector. type InputPath interface { DetectType(d ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) - Children() []*InputPath IsDir() bool - Walk(w func(i *InputPath) error) error - GetPath() string -} - -// InputDirectory is a concrete implementation of the InputPath interface that -// represents a directory. -type InputDirectory struct { - Path string - Name string - Contents []*InputPath -} - -func (i *InputDirectory) DetectType(d ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) { - return d.DetectDirectory(i, opts) -} - -func (i *InputDirectory) Children() []*InputPath { - return i.Contents -} - -func (i *InputDirectory) IsDir() bool { - return true -} - -func (i *InputDirectory) Walk(w func(i *InputPath) error) error { - for _, c := range i.Contents { - if err := w(c); err != nil { - return err - } - if err := (*c).Walk(w); err != nil { - return err - } - } - return nil -} - -func (i InputDirectory) GetPath() string { - return i.Path -} - -// NewInputDirectoryOptions contains options for instantiating a new InputDirectory -type NewInputDirectoryOptions struct { - Path string - Name string - NoIgnore bool - GitRepoFinder *git.RepoFinder -} - -func NewInputDirectory(opts NewInputDirectoryOptions) (*InputDirectory, error) { - contents := []*InputPath{} - entries, err := os.ReadDir(opts.Path) - if err != nil { - return nil, err - } - var repo *git2go.Repository - if !opts.NoIgnore { - repo = opts.GitRepoFinder.FindRepo(opts.Path) - } - for _, e := range entries { - n := e.Name() - p := filepath.Join(opts.Path, n) - if repo != nil { - if ignored, _ := repo.IsPathIgnored(p); ignored { - continue - } - } - var i InputPath - if e.IsDir() { - i, err = NewInputDirectory(NewInputDirectoryOptions{ - Path: p, - Name: n, - NoIgnore: opts.NoIgnore, - GitRepoFinder: opts.GitRepoFinder, - }) - if err != nil { - return nil, err - } - - } else { - i = NewInputFile(p, n) - } - contents = append(contents, &i) - } - return &InputDirectory{ - Path: opts.Path, - Name: opts.Name, - Contents: contents, - }, nil -} - -type InputFile struct { - Path string - Name string - Ext string - cachedContents []byte -} - -func (i *InputFile) DetectType(d ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) { - return d.DetectFile(i, opts) -} - -func (i *InputFile) Children() []*InputPath { - return nil -} - -func (i *InputFile) IsDir() bool { - return false -} - -func (i *InputFile) Walk(w func(i *InputPath) error) error { - return nil + Path() string + Name() string } -func (i *InputFile) GetPath() string { - return i.Path -} - -func (i *InputFile) ReadContents() ([]byte, error) { - if i.cachedContents != nil { - return i.cachedContents, nil - } - - if i.Name == StdIn { - contents, err := io.ReadAll(os.Stdin) - if err != nil { - i.cachedContents = []byte{} - return nil, err - } - i.cachedContents = contents - return contents, nil - } - - contents, err := os.ReadFile(i.Path) - if err != nil { - i.cachedContents = []byte{} - return nil, err - } - i.cachedContents = contents - return contents, nil +type InputDirectory interface { + InputPath + Walk(w func(i InputPath) error) error + Children() []InputPath } -func NewInputFile(path string, name string) *InputFile { - ext := filepath.Ext(path) - return &InputFile{ - Path: path, - Name: name, - Ext: ext, - } +type InputFile interface { + InputPath + Ext() string + Contents() ([]byte, error) } diff --git a/pkg/loader/cfnyaml.go b/pkg/loader/cfn.go similarity index 76% rename from pkg/loader/cfnyaml.go rename to pkg/loader/cfn.go index bfb235cd..1b2081b7 100644 --- a/pkg/loader/cfnyaml.go +++ b/pkg/loader/cfn.go @@ -21,47 +21,57 @@ import ( "gopkg.in/yaml.v3" ) -var CfnYAMLDetector = NewConfigurationDetector(&ConfigurationDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { - if !opts.IgnoreExt && i.Ext != ".yaml" && i.Ext != ".yml" { - return nil, fmt.Errorf("File does not have .yaml or .yml extension: %v", i.Path) - } - contents, err := i.ReadContents() - if err != nil { - return nil, err - } +var validCfnExts map[string]bool = map[string]bool{ + ".yaml": true, + ".yml": true, + ".json": true, +} - template := &cfnTemplate{} - if err := yaml.Unmarshal(contents, &template); err != nil { - return nil, fmt.Errorf("Failed to parse YAML file %v: %v", i.Path, err) - } - _, hasTemplateFormatVersion := template.Contents["AWSTemplateFormatVersion"] - _, hasResources := template.Contents["Resources"] +type CfnDetector struct{} - if !hasTemplateFormatVersion && !hasResources { - return nil, fmt.Errorf("Input file is not CloudFormation YAML: %v", i.Path) - } +func (c *CfnDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) { + if !opts.IgnoreExt && !validCfnExts[i.Ext()] { + return nil, fmt.Errorf("File does not have .yaml, .yml, or .json extension: %v", i.Path()) + } + contents, err := i.Contents() + if err != nil { + return nil, err + } - return &cfnYAML{ - path: i.Path, - template: *template, - }, nil - }, -}) + template := &cfnTemplate{} + if err := yaml.Unmarshal(contents, &template); err != nil { + return nil, fmt.Errorf("Failed to parse YAML file %v: %v", i.Path(), err) + } + _, hasTemplateFormatVersion := template.Contents["AWSTemplateFormatVersion"] + _, hasResources := template.Contents["Resources"] + + if !hasTemplateFormatVersion && !hasResources { + return nil, fmt.Errorf("Input file is not CloudFormation YAML: %v", i.Path()) + } + + return &cfnConfiguration{ + path: i.Path(), + template: *template, + }, nil +} + +func (c *CfnDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) { + return nil, nil +} -type cfnYAML struct { +type cfnConfiguration struct { path string template cfnTemplate } -func (l *cfnYAML) RegulaInput() RegulaInput { +func (l *cfnConfiguration) RegulaInput() RegulaInput { return RegulaInput{ "filepath": l.path, "content": l.template.Contents, } } -func (l *cfnYAML) Location(attributePath []string) (*Location, error) { +func (l *cfnConfiguration) Location(attributePath []string) (*Location, error) { return &Location{ Path: l.path, Line: 0, @@ -69,7 +79,7 @@ func (l *cfnYAML) Location(attributePath []string) (*Location, error) { }, nil } -func (l *cfnYAML) LoadedFiles() []string { +func (l *cfnConfiguration) LoadedFiles() []string { return []string{l.path} } diff --git a/pkg/loader/cfnjson.go b/pkg/loader/cfnjson.go deleted file mode 100644 index 6ed2287e..00000000 --- a/pkg/loader/cfnjson.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2021 Fugue, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package loader - -import ( - "encoding/json" - "fmt" -) - -var CfnJSONDetector = NewConfigurationDetector(&ConfigurationDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { - if !opts.IgnoreExt && i.Ext != ".json" { - return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) - } - contents, err := i.ReadContents() - if err != nil { - return nil, err - } - - c := &map[string]interface{}{} - if err := json.Unmarshal(contents, c); err != nil { - return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) - } - _, hasTemplateFormatVersion := (*c)["AWSTemplateFormatVersion"] - _, hasResources := (*c)["Resources"] - - if !hasTemplateFormatVersion && !hasResources { - return nil, fmt.Errorf("Input file is not CloudFormation JSON: %v", i.Path) - } - - return &cfnJSON{ - path: i.Path, - content: c, - }, nil - }, -}) - -type cfnJSON struct { - path string - content *map[string]interface{} -} - -func (l *cfnJSON) RegulaInput() RegulaInput { - return RegulaInput{ - "filepath": l.path, - "content": l.content, - } -} - -func (l *cfnJSON) Location(attributePath []string) (*Location, error) { - return &Location{ - Path: l.path, - Line: 0, - Col: 0, - }, nil -} - -func (l *cfnJSON) LoadedFiles() []string { - return []string{l.path} -} diff --git a/pkg/loader/input.go b/pkg/loader/input.go new file mode 100644 index 00000000..8bb71512 --- /dev/null +++ b/pkg/loader/input.go @@ -0,0 +1,163 @@ +package loader + +import ( + "io" + "os" + "path/filepath" + + "github.com/fugue/regula/pkg/git" +) + +type directory struct { + path string + name string + children []InputPath +} + +func (d *directory) DetectType(c ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) { + return c.DetectDirectory(d, opts) +} + +func (d *directory) IsDir() bool { + return true +} + +func (d *directory) Path() string { + return d.path +} + +func (d *directory) Name() string { + return d.name +} + +func (d *directory) Walk(w func(i InputPath) error) error { + for _, c := range d.children { + if err := w(c); err != nil { + return err + } + + if dir, ok := c.(InputDirectory); ok { + if err := dir.Walk(w); err != nil { + return err + } + } + } + return nil +} + +func (d *directory) Children() []InputPath { + return d.children +} + +type directoryOptions struct { + Path string + Name string + NoIgnore bool + GitRepoFinder *git.RepoFinder +} + +func newDirectory(opts directoryOptions) (InputDirectory, error) { + contents := []InputPath{} + entries, err := os.ReadDir(opts.Path) + if err != nil { + return nil, err + } + var repo git.Repo + if !opts.NoIgnore { + repo = opts.GitRepoFinder.FindRepo(opts.Path) + } + for _, e := range entries { + n := e.Name() + p := filepath.Join(opts.Path, n) + if repo != nil { + if ignored := repo.IsPathIgnored(p); ignored { + continue + } + } + var i InputPath + if e.IsDir() { + i, err = newDirectory(directoryOptions{ + Path: p, + Name: n, + NoIgnore: opts.NoIgnore, + GitRepoFinder: opts.GitRepoFinder, + }) + if err != nil { + return nil, err + } + + } else { + i = newFile(p, n) + } + contents = append(contents, i) + } + return &directory{ + path: opts.Path, + name: opts.Name, + children: contents, + }, nil +} + +type file struct { + path string + name string + ext string + cachedContents []byte +} + +func (f *file) DetectType(d ConfigurationDetector, opts DetectOptions) (IACConfiguration, error) { + return d.DetectFile(f, opts) +} + +func (f *file) IsDir() bool { + return false +} + +func (f *file) Path() string { + return f.path +} + +func (f *file) Name() string { + return f.name +} + +func (f *file) Walk(w func(i InputPath) error) error { + return nil +} + +func (f *file) Ext() string { + return f.ext +} + +func (f *file) Contents() ([]byte, error) { + if f.cachedContents != nil { + return f.cachedContents, nil + } + + if f.name == StdIn { + contents, err := io.ReadAll(os.Stdin) + if err != nil { + f.cachedContents = []byte{} + return nil, err + } + f.cachedContents = contents + return contents, nil + } + + contents, err := os.ReadFile(f.path) + if err != nil { + f.cachedContents = []byte{} + return nil, err + } + f.cachedContents = contents + return contents, nil +} + +func newFile(path string, name string) InputFile { + ext := filepath.Ext(path) + return &file{ + path: path, + name: name, + ext: ext, + } +} diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index fc1ba645..1e81144d 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -35,17 +35,16 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { if err != nil { return nil, err } - walkFunc := func(i *InputPath) error { - inputPath := *i - if configurations.AlreadyLoaded(inputPath.GetPath()) { + walkFunc := func(i InputPath) error { + if configurations.AlreadyLoaded(i.Path()) { return nil } // Ignore errors when we're recursing - loader, _ := inputPath.DetectType(*detector, DetectOptions{ + loader, _ := i.DetectType(detector, DetectOptions{ IgnoreExt: false, }) if loader != nil { - configurations.AddConfiguration(inputPath.GetPath(), loader) + configurations.AddConfiguration(i.Path(), loader) } return nil } @@ -58,12 +57,8 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { continue } if path == StdIn { - i := InputFile{ - Path: StdIn, - Name: StdIn, - Ext: "", - } - loader, err := i.DetectType(*detector, DetectOptions{ + i := newFile(StdIn, StdIn) + loader, err := i.DetectType(detector, DetectOptions{ IgnoreExt: true, }) if err != nil { @@ -71,7 +66,8 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { } if loader != nil { configurations.AddConfiguration(StdIn, loader) - // loaders[path] = loader + } else { + return nil, fmt.Errorf("Unable to detect input type of stdin") } continue } @@ -80,17 +76,17 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { if err != nil { return nil, err } - var i InputPath + // var i InputPath if info.IsDir() { // We want to override the gitignore behavior if the user explicitly gives // us a directory that is ignored. noIgnore := options.NoIgnore if !noIgnore { - repo := gitRepoFinder.FindRepo(path) - ignored, _ := repo.IsPathIgnored(path) - noIgnore = ignored + if repo := gitRepoFinder.FindRepo(path); repo != nil { + noIgnore = repo.IsPathIgnored(path) + } } - i, err = NewInputDirectory(NewInputDirectoryOptions{ + i, err := newDirectory(directoryOptions{ Path: path, Name: name, NoIgnore: noIgnore, @@ -99,20 +95,31 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { if err != nil { return nil, err } + loader, err := i.DetectType(detector, DetectOptions{ + IgnoreExt: options.InputType != Auto, + }) + if err != nil { + return nil, err + } + if loader != nil { + configurations.AddConfiguration(path, loader) + } + if err := i.Walk(walkFunc); err != nil { + return nil, err + } } else { - i = NewInputFile(path, name) - } - loader, err := i.DetectType(*detector, DetectOptions{ - IgnoreExt: options.InputType != Auto, - }) - if err != nil { - return nil, err - } - if loader != nil { - configurations.AddConfiguration(path, loader) - } - if err := i.Walk(walkFunc); err != nil { - return nil, err + i := newFile(path, name) + loader, err := i.DetectType(detector, DetectOptions{ + IgnoreExt: options.InputType != Auto, + }) + if err != nil { + return nil, err + } + if loader != nil { + configurations.AddConfiguration(path, loader) + } else { + return nil, fmt.Errorf("Unable to detect input type of file %v", i.Path()) + } } } if configurations.Count() < 1 { @@ -170,36 +177,18 @@ func (l *loadedConfigurations) Count() int { return len(l.configurations) } -func detectorByInputType(inputType InputType) (*ConfigurationDetector, error) { +func detectorByInputType(inputType InputType) (ConfigurationDetector, error) { switch inputType { case Auto: - return AutoDetector, nil - case CfnYAML: - return CfnYAMLDetector, nil - case CfnJSON: - return CfnJSONDetector, nil + return NewAutoDetector( + &CfnDetector{}, + &TfPlanDetector{}, + ), nil + case Cfn: + return &CfnDetector{}, nil case TfPlan: - return TfPlanDetector, nil + return &TfPlanDetector{}, nil default: return nil, fmt.Errorf("Unsupported input type: %v", inputType) } } - -var AutoDetector = NewConfigurationDetector(&ConfigurationDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { - l, err := i.DetectType(*TfPlanDetector, opts) - if err == nil { - return l, nil - } - l, err = i.DetectType(*CfnJSONDetector, opts) - if err == nil { - return l, nil - } - l, err = i.DetectType(*CfnYAMLDetector, opts) - if err == nil { - return l, nil - } - - return nil, fmt.Errorf("Unable to detect file type for file: %s", i.GetPath()) - }, -}) diff --git a/pkg/loader/tfplan.go b/pkg/loader/tfplan.go index f9751403..6eb8f17d 100644 --- a/pkg/loader/tfplan.go +++ b/pkg/loader/tfplan.go @@ -15,35 +15,40 @@ package loader import ( - "encoding/json" "fmt" + + "gopkg.in/yaml.v3" ) -var TfPlanDetector = NewConfigurationDetector(&ConfigurationDetector{ - DetectFile: func(i *InputFile, opts DetectOptions) (IACConfiguration, error) { - if !opts.IgnoreExt && i.Ext != ".json" { - return nil, fmt.Errorf("File does not have .json extension: %v", i.Path) - } - contents, err := i.ReadContents() - if err != nil { - return nil, err - } - c := &map[string]interface{}{} - if err := json.Unmarshal(contents, c); err != nil { - return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path, err) - } - _, hasTerraformVersion := (*c)["terraform_version"] +type TfPlanDetector struct{} + +func (t *TfPlanDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) { + if !opts.IgnoreExt && i.Ext() != ".json" { + return nil, fmt.Errorf("File does not have .json extension: %v", i.Path()) + } + contents, err := i.Contents() + if err != nil { + return nil, err + } + j := &map[string]interface{}{} + if err := yaml.Unmarshal(contents, j); err != nil { + return nil, fmt.Errorf("Failed to parse JSON file %v: %v", i.Path(), err) + } + _, hasTerraformVersion := (*j)["terraform_version"] + + if !hasTerraformVersion { + return nil, fmt.Errorf("Input file is not Terraform Plan JSON: %v", i.Path()) + } - if !hasTerraformVersion { - return nil, fmt.Errorf("Input file is not Terraform Plan JSON: %v", i.Path) - } + return &tfPlanLoader{ + path: i.Path(), + content: j, + }, nil +} - return &tfPlanLoader{ - path: i.Path, - content: c, - }, nil - }, -}) +func (c *TfPlanDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) { + return nil, nil +} type tfPlanLoader struct { path string From eaa545c87c549a5d076e55c10d6e22815f4cb0e2 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 29 Apr 2021 12:22:36 -0400 Subject: [PATCH 27/65] [RM-5352] Fix bug with gitignore behavior --- pkg/git/repo.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/git/repo.go b/pkg/git/repo.go index 4850812d..6dd41715 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -36,9 +36,9 @@ func (r *repo) IsPathIgnored(path string) bool { return false } // git2go's IsPathIgnored results differ from git check-ignore if you've got a - // pattern like we do where the path matches the name of the directory. It's - // safe to assume that this should always be false. - if path == r.path { + // pattern like we do where the path matches the name of the root directory for + // the repository. It's safe to assume that this should always be false. + if absPath == r.path { return false } ignored, _ := r.repo.IsPathIgnored(absPath) @@ -86,6 +86,7 @@ func (s *RepoFinder) FindRepo(path string) Repo { return nil } s.cache[absPath] = &repo{ + path: absPath, repo: r, } return s.cache[absPath] From d55fd3d38c3089e0cf31e104df410e53e0bfbd4b Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Fri, 30 Apr 2021 07:46:55 -0400 Subject: [PATCH 28/65] [RM-5352] In-progress: adding tests --- Makefile | 31 +++-- go.mod | 2 + go.sum | 5 + pkg/loader/base.go | 12 +- pkg/loader/cfn.go | 4 +- pkg/loader/cfn_test.go | 108 ++++++++++++++++ pkg/loader/input.go | 2 +- pkg/loader/loadpaths.go | 8 +- pkg/loader/test_inputs/cfn/cfn.json | 23 ++++ pkg/loader/test_inputs/cfn/cfn.yaml | 28 ++++ pkg/loader/test_inputs/cfn/cfn_resources.yaml | 26 ++++ pkg/loader/test_inputs/cfn/not_yaml.txt | 1 + pkg/loader/test_inputs/cfn/other.json | 4 + pkg/mocks/mock_configurationdetector.go | 65 ++++++++++ pkg/mocks/mock_iacconfiguration.go | 78 +++++++++++ pkg/mocks/mock_inputdirectory.go | 120 +++++++++++++++++ pkg/mocks/mock_inputfile.go | 121 ++++++++++++++++++ pkg/mocks/mock_inputpath.go | 92 +++++++++++++ .../{get-reporter.go => getreporter.go} | 0 19 files changed, 711 insertions(+), 19 deletions(-) create mode 100644 pkg/loader/cfn_test.go create mode 100644 pkg/loader/test_inputs/cfn/cfn.json create mode 100644 pkg/loader/test_inputs/cfn/cfn.yaml create mode 100644 pkg/loader/test_inputs/cfn/cfn_resources.yaml create mode 100644 pkg/loader/test_inputs/cfn/not_yaml.txt create mode 100644 pkg/loader/test_inputs/cfn/other.json create mode 100644 pkg/mocks/mock_configurationdetector.go create mode 100644 pkg/mocks/mock_iacconfiguration.go create mode 100644 pkg/mocks/mock_inputdirectory.go create mode 100644 pkg/mocks/mock_inputfile.go create mode 100644 pkg/mocks/mock_inputpath.go rename pkg/reporter/{get-reporter.go => getreporter.go} (100%) diff --git a/Makefile b/Makefile index 7dd95ba3..f8b2b4ea 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,21 @@ BINARY = regula INSTALLED_BINARY = /usr/local/bin/$(BINARY) -CLI_SOURCE = $(shell find cmd pkg -type f -name '*.go') $(wildcard cmd/*.txt) go.mod go.sum +GO_SOURCE = $(shell find cmd pkg -type f -name '*.go') +CLI_SOURCE = $(GO_SOURCE) $(wildcard cmd/*.txt) go.mod go.sum +# MOCKS_SOURCE = $(shell grep -L 'go:generate mockgen' $(GO_SOURCE)) +MOCKS = $(wildcard pkg/mocks/*.go) REGO_LIB_SOURCE = $(shell find rego/lib -type f -name '*.rego') REGO_RULES_SOURCE = $(shell find rego/rules -type f -name '*.rego') -GO = GO111MODULE=on go VERSION = $(shell cat VERSION) GITCOMMIT = $(shell git rev-parse --short HEAD 2> /dev/null || true) define LDFLAGS -X \"github.com/fugue/regula/cmd.Version=$(VERSION)\" \ -X \"github.com/fugue/regula/cmd.GitCommit=$(GITCOMMIT)\" endef -CLI_BUILD = $(GO) build -ldflags="$(LDFLAGS) -s -w" -GOLINT = $(shell go env GOPATH)/bin/golint +CLI_BUILD = go build -ldflags="$(LDFLAGS) -s -w" +GO_BIN_DIR= $(shell go env GOPATH)/bin +GOLINT = $(GO_BIN_DIR)/golint +MOCKGEN = $(GO_BIN_DIR)/mockgen COPIED_REGO_LIB = pkg/rego/lib COPIED_REGO_RULES = pkg/rego/rules @@ -22,7 +26,10 @@ $(COPIED_REGO_RULES): $(REGO_RULES_SOURCE) cp -R rego/rules $(COPIED_REGO_RULES) $(GOLINT): - $(GO) get -u golang.org/x/lint/golint + go install golang.org/x/lint/golint + +$(MOCKGEN): + go install github.com/golang/mock/mockgen@v1.5.0 $(BINARY): $(CLI_SOURCE) $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) $(CLI_BUILD) -v -o $@ @@ -36,11 +43,17 @@ $(BINARY)-darwin-amd64: $(SOURCE) $(INSTALLED_BINARY): $(BINARY) cp $(BINARY) $(INSTALLED_BINARY) +# $(MOCKS): $(MOCKGEN) $(MOCKS_SOURCE) +# PATH=$(GO_BIN_DIR):$(PATH) go generate ./... + release: $(BINARY)-linux-amd64 $(BINARY)-darwin-amd64 .PHONY: install install: $(INSTALLED_BINARY) +# .PHONY: mocks +# mocks: $(MOCKS) + .PHONY: clean clean: rm -f coverage.out @@ -48,17 +61,17 @@ clean: .PHONY: test test: - $(GO) test -v -cover ./... + go test -v -cover ./... .PHONY: coverage coverage: - $(GO) test ./... -coverprofile=coverage.out - $(GO) tool cover -html=coverage.out + go test ./... -coverprofile=coverage.out + go tool cover -html=coverage.out .PHONY: lint lint: $(GOLINT) ./... - $(GO) vet ./... + go vet ./... .PHONY: printsrc printsrc: diff --git a/go.mod b/go.mod index 2b73474c..77cfe0c8 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,13 @@ go 1.16 require ( github.com/alexeyco/simpletable v1.0.0 github.com/fatih/color v1.7.0 + github.com/golang/mock v1.5.0 github.com/kr/text v0.2.0 // indirect github.com/libgit2/git2go/v31 v31.4.14 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 github.com/spf13/cobra v1.1.3 + github.com/stretchr/testify v1.4.0 github.com/thediveo/enumflag v0.10.1 golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect diff --git a/go.sum b/go.sum index b64ca56d..be45dc74 100644 --- a/go.sum +++ b/go.sum @@ -67,6 +67,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= @@ -108,6 +109,8 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -276,6 +279,7 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -347,6 +351,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/thediveo/enumflag v0.10.1 h1:DB3Ag69VZ7BCv6jzKECrZ0ebZrHLzFRMIFYt96s4OxM= diff --git a/pkg/loader/base.go b/pkg/loader/base.go index d0f84789..6a6c9b46 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -14,8 +14,14 @@ package loader -// StdIn is the path used for stdin. -const StdIn = "" +//go:generate mockgen -destination=../mocks/mock_iacconfiguration.go -package=mocks github.com/fugue/regula/pkg/loader IACConfiguration +//go:generate mockgen -destination=../mocks/mock_configurationdetector.go -package=mocks github.com/fugue/regula/pkg/loader ConfigurationDetector +//go:generate mockgen -destination=../mocks/mock_inputpath.go -package=mocks github.com/fugue/regula/pkg/loader InputPath +//go:generate mockgen -destination=../mocks/mock_inputdirectory.go -package=mocks github.com/fugue/regula/pkg/loader InputDirectory +//go:generate mockgen -destination=../mocks/mock_inputfile.go -package=mocks github.com/fugue/regula/pkg/loader InputFile + +// stdIn is the path used for stdin. +const stdIn = "" // InputType is a flag that determines which types regula should look for. type InputType int @@ -28,7 +34,7 @@ const ( // directories and it will assume that given files are Terraform plan JSON. TfPlan // Cfn means that regula will only look for CloudFormation template files in given - // directories and it will assume that given files are CloudFormation JSON. + // directories and it will assume that given files are CloudFormation YAML or JSON. Cfn ) diff --git a/pkg/loader/cfn.go b/pkg/loader/cfn.go index 1b2081b7..0a0499bc 100644 --- a/pkg/loader/cfn.go +++ b/pkg/loader/cfn.go @@ -40,13 +40,13 @@ func (c *CfnDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfigurat template := &cfnTemplate{} if err := yaml.Unmarshal(contents, &template); err != nil { - return nil, fmt.Errorf("Failed to parse YAML file %v: %v", i.Path(), err) + return nil, fmt.Errorf("Failed to parse file as YAML or JSON %v: %v", i.Path(), err) } _, hasTemplateFormatVersion := template.Contents["AWSTemplateFormatVersion"] _, hasResources := template.Contents["Resources"] if !hasTemplateFormatVersion && !hasResources { - return nil, fmt.Errorf("Input file is not CloudFormation YAML: %v", i.Path()) + return nil, fmt.Errorf("Input file is not a CloudFormation template: %v", i.Path()) } return &cfnConfiguration{ diff --git a/pkg/loader/cfn_test.go b/pkg/loader/cfn_test.go new file mode 100644 index 00000000..48d7a4a4 --- /dev/null +++ b/pkg/loader/cfn_test.go @@ -0,0 +1,108 @@ +package loader_test + +import ( + _ "embed" + "testing" + + "github.com/fugue/regula/pkg/loader" + "github.com/fugue/regula/pkg/mocks" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +//go:embed test_inputs/cfn/cfn.yaml +var cfnYAMLContents []byte + +//go:embed test_inputs/cfn/cfn.json +var cfnJSONContents []byte + +//go:embed test_inputs/cfn/cfn_resources.yaml +var cfnYAMLResourcesContents []byte + +//go:embed test_inputs/cfn/other.json +var otherJSONContents []byte + +//go:embed test_inputs/cfn/not_yaml.txt +var notYAMLContents []byte + +func makeMockFile(ctrl *gomock.Controller, path, ext string, contents []byte) loader.InputFile { + mockFile := mocks.NewMockInputFile(ctrl) + mockFile.EXPECT().Ext().Return(ext) + mockFile.EXPECT().Path().Return(path) + mockFile.EXPECT().Contents().Return(contents, nil) + return mockFile +} + +func TestCfnDetector(t *testing.T) { + ctrl := gomock.NewController(t) + testInputs := []struct { + path string + ext string + contents []byte + }{ + {path: "cfn.yaml", ext: ".yaml", contents: cfnYAMLContents}, + {path: "cfn.yml", ext: ".yml", contents: cfnYAMLContents}, + {path: "cfn.json", ext: ".yaml", contents: cfnJSONContents}, + {path: "cfn_resources.yaml", ext: ".yaml", contents: cfnYAMLResourcesContents}, + } + detector := &loader.CfnDetector{} + + for _, i := range testInputs { + f := makeMockFile(ctrl, i.path, i.ext, i.contents) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.Nil(t, err) + assert.NotNil(t, loader) + assert.Equal(t, loader.LoadedFiles(), []string{i.path}) + } +} + +func TestCfnDetectorNotCfnContents(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.CfnDetector{} + f := makeMockFile(ctrl, "other.json", ".json", otherJSONContents) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.NotNil(t, err) + assert.Nil(t, loader) +} + +func TestCfnDetectorNotCfnExt(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.CfnDetector{} + f := mocks.NewMockInputFile(ctrl) + f.EXPECT().Ext().Return(".cfn") + f.EXPECT().Path().Return("cfn.cfn") + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.NotNil(t, err) + assert.Nil(t, loader) +} + +func TestCfnDetectorIgnoreExt(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.CfnDetector{} + f := mocks.NewMockInputFile(ctrl) + f.EXPECT().Path().Return("cfn.cfn") + f.EXPECT().Contents().Return(cfnYAMLContents, nil) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: true, + }) + assert.Nil(t, err) + assert.NotNil(t, loader) + assert.Equal(t, loader.LoadedFiles(), []string{"cfn.cfn"}) +} + +func TestCfnDetectorNotYAML(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.CfnDetector{} + f := makeMockFile(ctrl, "not_cfn.yaml", ".yaml", notYAMLContents) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.NotNil(t, err) + assert.Nil(t, loader) +} diff --git a/pkg/loader/input.go b/pkg/loader/input.go index 8bb71512..adbca9bc 100644 --- a/pkg/loader/input.go +++ b/pkg/loader/input.go @@ -134,7 +134,7 @@ func (f *file) Contents() ([]byte, error) { return f.cachedContents, nil } - if f.name == StdIn { + if f.name == stdIn { contents, err := io.ReadAll(os.Stdin) if err != nil { f.cachedContents = []byte{} diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 1e81144d..2e6a4d1d 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -51,13 +51,13 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { gitRepoFinder := git.NewRepoFinder() for _, path := range options.Paths { if path == "-" { - path = StdIn + path = stdIn } if configurations.AlreadyLoaded(path) { continue } - if path == StdIn { - i := newFile(StdIn, StdIn) + if path == stdIn { + i := newFile(stdIn, stdIn) loader, err := i.DetectType(detector, DetectOptions{ IgnoreExt: true, }) @@ -65,7 +65,7 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { return nil, err } if loader != nil { - configurations.AddConfiguration(StdIn, loader) + configurations.AddConfiguration(stdIn, loader) } else { return nil, fmt.Errorf("Unable to detect input type of stdin") } diff --git a/pkg/loader/test_inputs/cfn/cfn.json b/pkg/loader/test_inputs/cfn/cfn.json new file mode 100644 index 00000000..901778f9 --- /dev/null +++ b/pkg/loader/test_inputs/cfn/cfn.json @@ -0,0 +1,23 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Invalid S3 block public access configuration", + "Resources": { + "Bucket1": { + "Type": "AWS::S3::Bucket", + "Properties": { + "AccessControl": "Private" + } + }, + "Bucket2": { + "Type": "AWS::S3::Bucket", + "Properties": { + "AccessControl": "Private", + "PublicAccessBlockConfiguration": { + "BlockPublicAcls": true, + "IgnorePublicAcls": true, + "RestrictPublicBuckets": true + } + } + } + } +} \ No newline at end of file diff --git a/pkg/loader/test_inputs/cfn/cfn.yaml b/pkg/loader/test_inputs/cfn/cfn.yaml new file mode 100644 index 00000000..9bd689ff --- /dev/null +++ b/pkg/loader/test_inputs/cfn/cfn.yaml @@ -0,0 +1,28 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +AWSTemplateFormatVersion: "2010-09-09" +Description: Invalid S3 block public access configuration +Resources: + Bucket1: + Type: AWS::S3::Bucket + Properties: + AccessControl: Private + Bucket2: + Type: AWS::S3::Bucket + Properties: + AccessControl: Private + PublicAccessBlockConfiguration: + BlockPublicAcls: true + IgnorePublicAcls: true + RestrictPublicBuckets: true diff --git a/pkg/loader/test_inputs/cfn/cfn_resources.yaml b/pkg/loader/test_inputs/cfn/cfn_resources.yaml new file mode 100644 index 00000000..5421cf98 --- /dev/null +++ b/pkg/loader/test_inputs/cfn/cfn_resources.yaml @@ -0,0 +1,26 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +Resources: + Bucket1: + Type: AWS::S3::Bucket + Properties: + AccessControl: Private + Bucket2: + Type: AWS::S3::Bucket + Properties: + AccessControl: Private + PublicAccessBlockConfiguration: + BlockPublicAcls: true + IgnorePublicAcls: true + RestrictPublicBuckets: true diff --git a/pkg/loader/test_inputs/cfn/not_yaml.txt b/pkg/loader/test_inputs/cfn/not_yaml.txt new file mode 100644 index 00000000..f856c9f7 --- /dev/null +++ b/pkg/loader/test_inputs/cfn/not_yaml.txt @@ -0,0 +1 @@ +This file is not yaml or json. diff --git a/pkg/loader/test_inputs/cfn/other.json b/pkg/loader/test_inputs/cfn/other.json new file mode 100644 index 00000000..4964cd93 --- /dev/null +++ b/pkg/loader/test_inputs/cfn/other.json @@ -0,0 +1,4 @@ +{ + "foo": "bar", + "baz": 1 +} diff --git a/pkg/mocks/mock_configurationdetector.go b/pkg/mocks/mock_configurationdetector.go new file mode 100644 index 00000000..5f5edc78 --- /dev/null +++ b/pkg/mocks/mock_configurationdetector.go @@ -0,0 +1,65 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/fugue/regula/pkg/loader (interfaces: ConfigurationDetector) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + loader "github.com/fugue/regula/pkg/loader" + gomock "github.com/golang/mock/gomock" +) + +// MockConfigurationDetector is a mock of ConfigurationDetector interface. +type MockConfigurationDetector struct { + ctrl *gomock.Controller + recorder *MockConfigurationDetectorMockRecorder +} + +// MockConfigurationDetectorMockRecorder is the mock recorder for MockConfigurationDetector. +type MockConfigurationDetectorMockRecorder struct { + mock *MockConfigurationDetector +} + +// NewMockConfigurationDetector creates a new mock instance. +func NewMockConfigurationDetector(ctrl *gomock.Controller) *MockConfigurationDetector { + mock := &MockConfigurationDetector{ctrl: ctrl} + mock.recorder = &MockConfigurationDetectorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConfigurationDetector) EXPECT() *MockConfigurationDetectorMockRecorder { + return m.recorder +} + +// DetectDirectory mocks base method. +func (m *MockConfigurationDetector) DetectDirectory(arg0 loader.InputDirectory, arg1 loader.DetectOptions) (loader.IACConfiguration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DetectDirectory", arg0, arg1) + ret0, _ := ret[0].(loader.IACConfiguration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DetectDirectory indicates an expected call of DetectDirectory. +func (mr *MockConfigurationDetectorMockRecorder) DetectDirectory(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectDirectory", reflect.TypeOf((*MockConfigurationDetector)(nil).DetectDirectory), arg0, arg1) +} + +// DetectFile mocks base method. +func (m *MockConfigurationDetector) DetectFile(arg0 loader.InputFile, arg1 loader.DetectOptions) (loader.IACConfiguration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DetectFile", arg0, arg1) + ret0, _ := ret[0].(loader.IACConfiguration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DetectFile indicates an expected call of DetectFile. +func (mr *MockConfigurationDetectorMockRecorder) DetectFile(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectFile", reflect.TypeOf((*MockConfigurationDetector)(nil).DetectFile), arg0, arg1) +} diff --git a/pkg/mocks/mock_iacconfiguration.go b/pkg/mocks/mock_iacconfiguration.go new file mode 100644 index 00000000..8af89008 --- /dev/null +++ b/pkg/mocks/mock_iacconfiguration.go @@ -0,0 +1,78 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/fugue/regula/pkg/loader (interfaces: IACConfiguration) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + loader "github.com/fugue/regula/pkg/loader" + gomock "github.com/golang/mock/gomock" +) + +// MockIACConfiguration is a mock of IACConfiguration interface. +type MockIACConfiguration struct { + ctrl *gomock.Controller + recorder *MockIACConfigurationMockRecorder +} + +// MockIACConfigurationMockRecorder is the mock recorder for MockIACConfiguration. +type MockIACConfigurationMockRecorder struct { + mock *MockIACConfiguration +} + +// NewMockIACConfiguration creates a new mock instance. +func NewMockIACConfiguration(ctrl *gomock.Controller) *MockIACConfiguration { + mock := &MockIACConfiguration{ctrl: ctrl} + mock.recorder = &MockIACConfigurationMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockIACConfiguration) EXPECT() *MockIACConfigurationMockRecorder { + return m.recorder +} + +// LoadedFiles mocks base method. +func (m *MockIACConfiguration) LoadedFiles() []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadedFiles") + ret0, _ := ret[0].([]string) + return ret0 +} + +// LoadedFiles indicates an expected call of LoadedFiles. +func (mr *MockIACConfigurationMockRecorder) LoadedFiles() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadedFiles", reflect.TypeOf((*MockIACConfiguration)(nil).LoadedFiles)) +} + +// Location mocks base method. +func (m *MockIACConfiguration) Location(arg0 []string) (*loader.Location, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Location", arg0) + ret0, _ := ret[0].(*loader.Location) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Location indicates an expected call of Location. +func (mr *MockIACConfigurationMockRecorder) Location(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Location", reflect.TypeOf((*MockIACConfiguration)(nil).Location), arg0) +} + +// RegulaInput mocks base method. +func (m *MockIACConfiguration) RegulaInput() loader.RegulaInput { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RegulaInput") + ret0, _ := ret[0].(loader.RegulaInput) + return ret0 +} + +// RegulaInput indicates an expected call of RegulaInput. +func (mr *MockIACConfigurationMockRecorder) RegulaInput() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RegulaInput", reflect.TypeOf((*MockIACConfiguration)(nil).RegulaInput)) +} diff --git a/pkg/mocks/mock_inputdirectory.go b/pkg/mocks/mock_inputdirectory.go new file mode 100644 index 00000000..4402d8fa --- /dev/null +++ b/pkg/mocks/mock_inputdirectory.go @@ -0,0 +1,120 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/fugue/regula/pkg/loader (interfaces: InputDirectory) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + loader "github.com/fugue/regula/pkg/loader" + gomock "github.com/golang/mock/gomock" +) + +// MockInputDirectory is a mock of InputDirectory interface. +type MockInputDirectory struct { + ctrl *gomock.Controller + recorder *MockInputDirectoryMockRecorder +} + +// MockInputDirectoryMockRecorder is the mock recorder for MockInputDirectory. +type MockInputDirectoryMockRecorder struct { + mock *MockInputDirectory +} + +// NewMockInputDirectory creates a new mock instance. +func NewMockInputDirectory(ctrl *gomock.Controller) *MockInputDirectory { + mock := &MockInputDirectory{ctrl: ctrl} + mock.recorder = &MockInputDirectoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInputDirectory) EXPECT() *MockInputDirectoryMockRecorder { + return m.recorder +} + +// Children mocks base method. +func (m *MockInputDirectory) Children() []loader.InputPath { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Children") + ret0, _ := ret[0].([]loader.InputPath) + return ret0 +} + +// Children indicates an expected call of Children. +func (mr *MockInputDirectoryMockRecorder) Children() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Children", reflect.TypeOf((*MockInputDirectory)(nil).Children)) +} + +// DetectType mocks base method. +func (m *MockInputDirectory) DetectType(arg0 loader.ConfigurationDetector, arg1 loader.DetectOptions) (loader.IACConfiguration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DetectType", arg0, arg1) + ret0, _ := ret[0].(loader.IACConfiguration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DetectType indicates an expected call of DetectType. +func (mr *MockInputDirectoryMockRecorder) DetectType(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectType", reflect.TypeOf((*MockInputDirectory)(nil).DetectType), arg0, arg1) +} + +// IsDir mocks base method. +func (m *MockInputDirectory) IsDir() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsDir") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsDir indicates an expected call of IsDir. +func (mr *MockInputDirectoryMockRecorder) IsDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDir", reflect.TypeOf((*MockInputDirectory)(nil).IsDir)) +} + +// Name mocks base method. +func (m *MockInputDirectory) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockInputDirectoryMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockInputDirectory)(nil).Name)) +} + +// Path mocks base method. +func (m *MockInputDirectory) Path() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Path") + ret0, _ := ret[0].(string) + return ret0 +} + +// Path indicates an expected call of Path. +func (mr *MockInputDirectoryMockRecorder) Path() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Path", reflect.TypeOf((*MockInputDirectory)(nil).Path)) +} + +// Walk mocks base method. +func (m *MockInputDirectory) Walk(arg0 func(loader.InputPath) error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Walk", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Walk indicates an expected call of Walk. +func (mr *MockInputDirectoryMockRecorder) Walk(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Walk", reflect.TypeOf((*MockInputDirectory)(nil).Walk), arg0) +} diff --git a/pkg/mocks/mock_inputfile.go b/pkg/mocks/mock_inputfile.go new file mode 100644 index 00000000..7485d2a6 --- /dev/null +++ b/pkg/mocks/mock_inputfile.go @@ -0,0 +1,121 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/fugue/regula/pkg/loader (interfaces: InputFile) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + loader "github.com/fugue/regula/pkg/loader" + gomock "github.com/golang/mock/gomock" +) + +// MockInputFile is a mock of InputFile interface. +type MockInputFile struct { + ctrl *gomock.Controller + recorder *MockInputFileMockRecorder +} + +// MockInputFileMockRecorder is the mock recorder for MockInputFile. +type MockInputFileMockRecorder struct { + mock *MockInputFile +} + +// NewMockInputFile creates a new mock instance. +func NewMockInputFile(ctrl *gomock.Controller) *MockInputFile { + mock := &MockInputFile{ctrl: ctrl} + mock.recorder = &MockInputFileMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInputFile) EXPECT() *MockInputFileMockRecorder { + return m.recorder +} + +// Contents mocks base method. +func (m *MockInputFile) Contents() ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Contents") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Contents indicates an expected call of Contents. +func (mr *MockInputFileMockRecorder) Contents() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Contents", reflect.TypeOf((*MockInputFile)(nil).Contents)) +} + +// DetectType mocks base method. +func (m *MockInputFile) DetectType(arg0 loader.ConfigurationDetector, arg1 loader.DetectOptions) (loader.IACConfiguration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DetectType", arg0, arg1) + ret0, _ := ret[0].(loader.IACConfiguration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DetectType indicates an expected call of DetectType. +func (mr *MockInputFileMockRecorder) DetectType(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectType", reflect.TypeOf((*MockInputFile)(nil).DetectType), arg0, arg1) +} + +// Ext mocks base method. +func (m *MockInputFile) Ext() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Ext") + ret0, _ := ret[0].(string) + return ret0 +} + +// Ext indicates an expected call of Ext. +func (mr *MockInputFileMockRecorder) Ext() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Ext", reflect.TypeOf((*MockInputFile)(nil).Ext)) +} + +// IsDir mocks base method. +func (m *MockInputFile) IsDir() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsDir") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsDir indicates an expected call of IsDir. +func (mr *MockInputFileMockRecorder) IsDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDir", reflect.TypeOf((*MockInputFile)(nil).IsDir)) +} + +// Name mocks base method. +func (m *MockInputFile) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockInputFileMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockInputFile)(nil).Name)) +} + +// Path mocks base method. +func (m *MockInputFile) Path() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Path") + ret0, _ := ret[0].(string) + return ret0 +} + +// Path indicates an expected call of Path. +func (mr *MockInputFileMockRecorder) Path() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Path", reflect.TypeOf((*MockInputFile)(nil).Path)) +} diff --git a/pkg/mocks/mock_inputpath.go b/pkg/mocks/mock_inputpath.go new file mode 100644 index 00000000..e3864a9b --- /dev/null +++ b/pkg/mocks/mock_inputpath.go @@ -0,0 +1,92 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/fugue/regula/pkg/loader (interfaces: InputPath) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + loader "github.com/fugue/regula/pkg/loader" + gomock "github.com/golang/mock/gomock" +) + +// MockInputPath is a mock of InputPath interface. +type MockInputPath struct { + ctrl *gomock.Controller + recorder *MockInputPathMockRecorder +} + +// MockInputPathMockRecorder is the mock recorder for MockInputPath. +type MockInputPathMockRecorder struct { + mock *MockInputPath +} + +// NewMockInputPath creates a new mock instance. +func NewMockInputPath(ctrl *gomock.Controller) *MockInputPath { + mock := &MockInputPath{ctrl: ctrl} + mock.recorder = &MockInputPathMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockInputPath) EXPECT() *MockInputPathMockRecorder { + return m.recorder +} + +// DetectType mocks base method. +func (m *MockInputPath) DetectType(arg0 loader.ConfigurationDetector, arg1 loader.DetectOptions) (loader.IACConfiguration, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DetectType", arg0, arg1) + ret0, _ := ret[0].(loader.IACConfiguration) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DetectType indicates an expected call of DetectType. +func (mr *MockInputPathMockRecorder) DetectType(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectType", reflect.TypeOf((*MockInputPath)(nil).DetectType), arg0, arg1) +} + +// IsDir mocks base method. +func (m *MockInputPath) IsDir() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsDir") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsDir indicates an expected call of IsDir. +func (mr *MockInputPathMockRecorder) IsDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDir", reflect.TypeOf((*MockInputPath)(nil).IsDir)) +} + +// Name mocks base method. +func (m *MockInputPath) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name. +func (mr *MockInputPathMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockInputPath)(nil).Name)) +} + +// Path mocks base method. +func (m *MockInputPath) Path() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Path") + ret0, _ := ret[0].(string) + return ret0 +} + +// Path indicates an expected call of Path. +func (mr *MockInputPathMockRecorder) Path() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Path", reflect.TypeOf((*MockInputPath)(nil).Path)) +} diff --git a/pkg/reporter/get-reporter.go b/pkg/reporter/getreporter.go similarity index 100% rename from pkg/reporter/get-reporter.go rename to pkg/reporter/getreporter.go From 4657aefef753d56e11f5cb5e842e8b4bfffa3cb3 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Fri, 30 Apr 2021 12:56:46 -0400 Subject: [PATCH 29/65] [RM-5352] About to clean up --- cmd/repl.go | 146 +++++++++++++++++++++++ go.sum | 15 +++ pkg/loader/loadpaths.go | 1 - pkg/rego/load.go | 192 +++++++++++++++++++++++++++++++ pkg/rego/repl.go | 235 ++++++++++++++++++++++++++++++++++++++ pkg/rego/runner.go | 110 ------------------ pkg/rego/test_helper.rego | 10 ++ 7 files changed, 598 insertions(+), 111 deletions(-) create mode 100644 cmd/repl.go create mode 100644 pkg/rego/load.go create mode 100644 pkg/rego/repl.go create mode 100644 pkg/rego/test_helper.rego diff --git a/cmd/repl.go b/cmd/repl.go new file mode 100644 index 00000000..4aec68ec --- /dev/null +++ b/cmd/repl.go @@ -0,0 +1,146 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + _ "embed" + "fmt" + "os" + + "github.com/fugue/regula/pkg/loader" + "github.com/fugue/regula/pkg/rego" + + // "github.com/fugue/regula/pkg/reporter" + "github.com/spf13/cobra" + "github.com/thediveo/enumflag" + // "golang.org/x/crypto/ssh/terminal" +) + +func NewREPLCommand() *cobra.Command { + var inputType loader.InputType + // var format reporter.Format + // severity := reporter.Unknown + cmd := &cobra.Command{ + Use: "repl", + Short: "Evaluate rules against infrastructure-as-code with Regula.", + Run: func(cmd *cobra.Command, paths []string) { + includes, err := cmd.Flags().GetStringSlice("include") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + userOnly, err := cmd.Flags().GetBool("user-only") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + // noIgnore, err := cmd.Flags().GetBool("no-ignore") + // if err != nil { + // fmt.Println(err) + // os.Exit(1) + // } + ctx := context.TODO() + // repl, err := rego.NewREPL(®o.RuleRunnerOptions{ + // Ctx: ctx, + // UserOnly: userOnly, + // Includes: includes, + // }) + err = rego.NewREPL(®o.RuleRunnerOptions{ + Ctx: ctx, + UserOnly: userOnly, + Includes: includes, + }) + // ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ + // Ctx: ctx, + // UserOnly: userOnly, + // Includes: includes, + // }) + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // repl.Loop(ctx) + + // if len(paths) < 1 { + // if terminal.IsTerminal(int(os.Stdin.Fd())) { + // // Not using os.Getwd here so that we get relative paths. + // // A single dot should mean the same on windows. + // paths = []string{"."} + // } else { + // paths = []string{"-"} + // } + // } + + // loadedFiles, err := loader.LoadPaths(loader.LoadPathsOptions{ + // Paths: paths, + // InputType: inputType, + // NoIgnore: noIgnore, + // }) + // if err != nil { + // fmt.Println(err) + // os.Exit(1) + // } + + // results, err := ruleRunner.Run(loadedFiles.RegulaInput()) + // if err != nil { + // fmt.Println(err) + // os.Exit(1) + // } + + // reporterFunc, _ := reporter.GetReporter(format) + // r := results[0] + // output, err := reporter.ParseRegulaOutput(loadedFiles, r) + // if err != nil { + // fmt.Println(err) + // os.Exit(1) + // } + // report, err := reporterFunc(output) + // if err != nil { + // fmt.Println(err) + // os.Exit(1) + // } + // if report != "" { + // fmt.Println(report) + // } + // if output.ExceedsSeverity(severity) { + // os.Exit(1) + // } + }, + } + + cmd.Flags().StringSliceP("include", "i", nil, "Specify additional rego files or directories to include") + cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") + // cmd.Flags().BoolP("no-ignore", "n", false, "Disable use of .gitignore") + cmd.Flags().VarP( + enumflag.New(&inputType, "input-type", loader.InputTypeIDs, enumflag.EnumCaseInsensitive), + "input-type", "t", + "Set the input type for the given paths") + // cmd.Flags().VarP( + // enumflag.New(&severity, "severity", reporter.SeverityIds, enumflag.EnumCaseInsensitive), + // "severity", "s", + // "Set the minimum severity that will result in a non-zero exit code.") + // cmd.Flags().VarP( + // enumflag.New(&format, "format", reporter.FormatIds, enumflag.EnumCaseInsensitive), + // "format", "f", + // "Set the output format") + return cmd +} + +func init() { + rootCmd.AddCommand(NewREPLCommand()) +} diff --git a/go.sum b/go.sum index be45dc74..14f32714 100644 --- a/go.sum +++ b/go.sum @@ -40,13 +40,16 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -84,6 +87,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -120,6 +124,7 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -127,6 +132,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -141,6 +147,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -217,6 +224,7 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -246,6 +254,7 @@ github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtb github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -271,6 +280,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d h1:zapSxdmZYY6vJWXFKLQ+MkI+agc+HQyfrCGowDSHiKs= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -287,12 +297,14 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -300,6 +312,7 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -307,6 +320,7 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -554,6 +568,7 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 2e6a4d1d..2cb6b710 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -76,7 +76,6 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { if err != nil { return nil, err } - // var i InputPath if info.IsDir() { // We want to override the gitignore behavior if the user explicitly gives // us a directory that is ignored. diff --git a/pkg/rego/load.go b/pkg/rego/load.go new file mode 100644 index 00000000..23c4e481 --- /dev/null +++ b/pkg/rego/load.go @@ -0,0 +1,192 @@ +package rego + +import ( + "embed" + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/fugue/regula/pkg/loader" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/types" +) + +//go:embed lib +var regulaLib embed.FS + +//go:embed rules +var regulaRules embed.FS + +var loadExts map[string]bool = map[string]bool{ + ".rego": true, + ".yaml": true, + ".yml": true, + ".json": true, +} + +func loadModule(fsys fs.FS, path string) (func(r *rego.Rego), error) { + contents, err := fs.ReadFile(fsys, path) + if err != nil { + return nil, err + } + return rego.Module(path, string(contents)), nil +} + +func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { + modules := []func(r *rego.Rego){} + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); !loadExts[ext] { + return nil + } + module, err := loadModule(fsys, path) + if err != nil { + return err + } + modules = append(modules, module) + return nil + } + + if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { + return nil, err + } + + return modules, nil +} + +func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { + modules := []func(r *rego.Rego){} + o := osFs{} + for _, path := range includes { + info, err := fs.Stat(o, path) + if err != nil { + return nil, err + } + if info.IsDir() { + dirModules, err := loadDirectory(o, path) + if err != nil { + return nil, err + } + + modules = append(modules, dirModules...) + continue + } + if ext := filepath.Ext(path); !loadExts[ext] { + return nil, fmt.Errorf("Unsupported file type %v in includes: %v", ext, path) + } + module, err := loadModule(o, path) + if err != nil { + return nil, err + } + if module != nil { + modules = append(modules, module) + } + } + + return modules, nil +} + +func loadRegula(userOnly bool) ([]func(r *rego.Rego), error) { + modules, err := loadDirectory(regulaLib, "lib") + if err != nil { + return nil, err + } + + if !userOnly { + rules, err := loadDirectory(regulaRules, "rules") + if err != nil { + return nil, err + } + modules = append(modules, rules...) + } + return modules, nil +} + +func defineLoadFunction() func(r *rego.Rego) { + return rego.Function1( + ®o.Function{ + Name: "regula.load", + Decl: types.NewFunction(types.Args(types.S), types.A), + }, + func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + if str, ok := a.Value.(ast.String); ok { + path := string(str) + configs, err := loader.LoadPaths(loader.LoadPathsOptions{ + Paths: []string{path}, + InputType: loader.Auto, + NoIgnore: false, + }) + if err != nil { + return nil, err + } + + v, err := ast.InterfaceToValue(configs.RegulaInput()) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + } + return nil, nil + }, + ) +} + +func defineLoadFunction2() { + rego.RegisterBuiltin1( + ®o.Function{ + Name: "regula.load", + Decl: types.NewFunction(types.Args(types.S), types.A), + Memoize: true, + }, + func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + var path string + if err := ast.As(a.Value, &path); err != nil { + return nil, err + } + + configs, err := loader.LoadPaths(loader.LoadPathsOptions{ + Paths: []string{path}, + InputType: loader.Auto, + NoIgnore: false, + }) + if err != nil { + return nil, err + } + + // testVal := map[string]string{ + // "foo": "bar", + // } + testVal := []interface{}{} + + bts, _ := json.Marshal(configs.RegulaInput()) + _ = json.Unmarshal(bts, &testVal) + + // v, err := ast.InterfaceToValue(configs.RegulaInput()) + v, err := ast.InterfaceToValue(testVal) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil + }, + ) +} + +type osFs struct{} + +func (o osFs) Open(name string) (fs.File, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + return f, nil +} diff --git a/pkg/rego/repl.go b/pkg/rego/repl.go new file mode 100644 index 00000000..d90f86a9 --- /dev/null +++ b/pkg/rego/repl.go @@ -0,0 +1,235 @@ +package rego + +import ( + _ "embed" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/bundle" + "github.com/open-policy-agent/opa/metrics" + "github.com/open-policy-agent/opa/repl" + "github.com/open-policy-agent/opa/runtime" + "github.com/open-policy-agent/opa/storage" + "github.com/open-policy-agent/opa/storage/inmem" +) + +//go:embed test_helper.rego +var testHelperRego []byte + +func loadDirectoryRaw(fsys fs.FS, path string) (map[string][]byte, error) { + modules := map[string][]byte{} + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); !loadExts[ext] { + return nil + } + raw, err := fs.ReadFile(fsys, path) + if err != nil { + return err + } + modules[path] = raw + return nil + } + if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { + return nil, err + } + + return modules, nil +} + +func loadAstModule(fsys fs.FS, path string) (*ast.Module, error) { + contents, err := fs.ReadFile(fsys, path) + if err != nil { + return nil, err + } + return ast.ParseModule(path, string(contents)) +} + +func loadDirectoryAst(fsys fs.FS, path string) (map[string]*ast.Module, error) { + modules := map[string]*ast.Module{} + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); !loadExts[ext] { + return nil + } + module, err := loadAstModule(fsys, path) + if err != nil { + return err + } + modules[path] = module + return nil + } + if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { + return nil, err + } + + return modules, nil +} + +func loadRegulaAst() (map[string]*ast.Module, error) { + libModules, err := loadDirectoryAst(regulaLib, "lib") + if err != nil { + return nil, err + } + rulesModules, err := loadDirectoryAst(regulaRules, "rules") + if err != nil { + return nil, err + } + for k, v := range rulesModules { + libModules[k] = v + } + return libModules, nil +} + +func loadRegulaRaw() (map[string][]byte, error) { + libModules, err := loadDirectoryRaw(regulaLib, "lib") + if err != nil { + return nil, err + } + rulesModules, err := loadDirectoryRaw(regulaRules, "rules") + if err != nil { + return nil, err + } + for k, v := range rulesModules { + libModules[k] = v + } + libModules["test_helper.rego"] = testHelperRego + return libModules, nil +} + +func initStore(options *RuleRunnerOptions) (storage.Store, error) { + modules, err := loadRegulaAst() + if err != nil { + return nil, err + } + + store := inmem.New() + txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ + Write: true, + }) + if err != nil { + return nil, err + } + c := ast.NewCompiler() + // txn := storage.New(options.Ctx, store) + defer store.Abort(options.Ctx, txn) + bundleOpts := &bundle.ActivateOpts{ + Ctx: options.Ctx, + Store: store, + Txn: txn, + Compiler: c, + Metrics: metrics.New(), + Bundles: map[string]*bundle.Bundle{}, + ExtraModules: modules, + } + if err := bundle.Activate(bundleOpts); err != nil { + return nil, err + } + // if err := store.Commit(options.Ctx, txn); err != nil { + // return nil, err + // } + return store, nil +} + +func initStore2(options *RuleRunnerOptions) (storage.Store, error) { + modules, err := loadRegulaRaw() + if err != nil { + return nil, err + } + store := inmem.New() + txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ + Write: true, + }) + if err != nil { + return nil, err + } + // defer store.Abort(options.Ctx, txn) + for path, raw := range modules { + if err := store.UpsertPolicy(options.Ctx, txn, path, raw); err != nil { + return nil, err + } + } + if err := store.Commit(options.Ctx, txn); err != nil { + return nil, err + } + return store, nil +} + +func NewREPL(options *RuleRunnerOptions) error { + defineLoadFunction2() + store, err := initStore2(options) + if err != nil { + return err + } + txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ + Write: false, + }) + defer store.Abort(options.Ctx, txn) + if err != nil { + return err + } + policies, err := store.ListPolicies(options.Ctx, txn) + if err != nil { + return err + } + fmt.Println(policies) + // regula, err := loadRegula(options.UserOnly) + // if err != nil { + // return nil, fmt.Errorf("Failed to load Regula: %v", err) + // } + + // includes, err := loadIncludes(options.Includes) + // if err != nil { + // return nil, fmt.Errorf("Failed to load includes: %v", err) + // } + // regoFuncs := []func(r *rego.Rego){rego.Store(store)} + // regoFuncs = append(regoFuncs, regula...) + // regoFuncs = append(regoFuncs, includes...) + + // // regoFuncs = append(regoFuncs, defineLoadFunction()) + // // regoFuncs = append(regoFuncs, ) + // regoFuncs = append(regoFuncs, rego.Query("data.fugue.regula.report")) + // _, err = rego.New( + // regoFuncs..., + // ).PrepareForPartial(options.Ctx) + // // _, err = query.Compile(options.Ctx, rego.CompilePartial(false)) + // if err != nil { + // return nil, err + // } + + // policies, err := store.ListPolicies(options.Ctx, txn) + // if err != nil { + // return err + // } + // fmt.Println(policies) + + r := repl.New(store, "./.regula-history", os.Stdout, "", 50, "foo") + r.Loop(options.Ctx) + return nil +} + +func StartREPL(options *RuleRunnerOptions) error { + defineLoadFunction2() + r, err := runtime.NewRuntime(options.Ctx, runtime.Params{ + Output: os.Stdout, + }) + if err != nil { + return err + } + + r.StartREPL(options.Ctx) + return nil +} diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go index 818e7813..efcf4c31 100644 --- a/pkg/rego/runner.go +++ b/pkg/rego/runner.go @@ -16,22 +16,12 @@ package rego import ( "context" - "embed" "fmt" - "io/fs" - "os" - "path/filepath" "github.com/fugue/regula/pkg/loader" "github.com/open-policy-agent/opa/rego" ) -//go:embed lib -var regulaLib embed.FS - -//go:embed rules -var regulaRules embed.FS - // RuleRunner wraps the logic to load Regula into OPA and evaluate rules // against an input type RuleRunner struct { @@ -47,96 +37,6 @@ type RuleRunnerOptions struct { Debug bool } -var LoadExts map[string]bool = map[string]bool{ - ".rego": true, - ".yaml": true, - ".yml": true, - ".json": true, -} - -func loadModule(fsys fs.FS, path string) (func(r *rego.Rego), error) { - contents, err := fs.ReadFile(fsys, path) - if err != nil { - return nil, err - } - return rego.Module(path, string(contents)), nil -} - -func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { - modules := []func(r *rego.Rego){} - walkDirFunc := func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if ext := filepath.Ext(path); !LoadExts[ext] { - return nil - } - module, err := loadModule(fsys, path) - if err != nil { - return err - } - modules = append(modules, module) - return nil - } - - if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { - return nil, err - } - - return modules, nil -} - -func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { - modules := []func(r *rego.Rego){} - o := osFs{} - for _, path := range includes { - info, err := fs.Stat(o, path) - if err != nil { - return nil, err - } - if info.IsDir() { - dirModules, err := loadDirectory(o, path) - if err != nil { - return nil, err - } - - modules = append(modules, dirModules...) - continue - } - if ext := filepath.Ext(path); !LoadExts[ext] { - return nil, fmt.Errorf("Unsupported file type %v in includes: %v", ext, path) - } - module, err := loadModule(o, path) - if err != nil { - return nil, err - } - if module != nil { - modules = append(modules, module) - } - } - - return modules, nil -} - -func loadRegula(userOnly bool) ([]func(r *rego.Rego), error) { - modules, err := loadDirectory(regulaLib, "lib") - if err != nil { - return nil, err - } - - if !userOnly { - rules, err := loadDirectory(regulaRules, "rules") - if err != nil { - return nil, err - } - modules = append(modules, rules...) - } - return modules, nil -} - // NewRuleRunner instantiates a new RuleRunner func NewRuleRunner(options *RuleRunnerOptions) (*RuleRunner, error) { // paths := append(options.RulesDirs, options.LibraryDir) @@ -175,13 +75,3 @@ func (r *RuleRunner) Run(input []loader.RegulaInput) (rego.ResultSet, error) { return results, nil } - -type osFs struct{} - -func (o osFs) Open(name string) (fs.File, error) { - f, err := os.Open(name) - if err != nil { - return nil, err - } - return f, nil -} diff --git a/pkg/rego/test_helper.rego b/pkg/rego/test_helper.rego new file mode 100644 index 00000000..974557ac --- /dev/null +++ b/pkg/rego/test_helper.rego @@ -0,0 +1,10 @@ +package regula + +import data.fugue.resource_view.resource_view_input + +mock_input(path) = ret { + iac_configs := regula.load(path) + iac_config = iac_configs[_] + iac_config.filepath == path + ret = resource_view_input with input as iac_config.content +} From b2c9551b630157c59e9d0c371c9e50b9871cb3e6 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 3 May 2021 09:33:55 -0400 Subject: [PATCH 30/65] [RM-5352] Working test and repl, but needs to be cleaned up --- .gitignore | 1 + Makefile | 2 +- cmd/repl.go | 84 +----------- cmd/test.go | 49 +++++++ pkg/loader/loadpaths.go | 4 +- pkg/rego/load.go | 269 +++++++++++++++++++++++++++++--------- pkg/rego/repl.go | 225 +------------------------------ pkg/rego/test_helper.rego | 4 +- pkg/rego/tester.go | 56 ++++++++ 9 files changed, 325 insertions(+), 369 deletions(-) create mode 100644 cmd/test.go create mode 100644 pkg/rego/tester.go diff --git a/.gitignore b/.gitignore index c7411647..42bef8e7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ regula regula-* .vscode/ .scratch/ +.regula-history diff --git a/Makefile b/Makefile index f8b2b4ea..eb4bb74d 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ $(GOLINT): $(MOCKGEN): go install github.com/golang/mock/mockgen@v1.5.0 -$(BINARY): $(CLI_SOURCE) $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) +$(BINARY): $(CLI_SOURCE) $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) pkg/rego/test_helper.rego $(CLI_BUILD) -v -o $@ $(BINARY)-linux-amd64: $(SOURCE) diff --git a/cmd/repl.go b/cmd/repl.go index 4aec68ec..c1027839 100644 --- a/cmd/repl.go +++ b/cmd/repl.go @@ -20,19 +20,12 @@ import ( "fmt" "os" - "github.com/fugue/regula/pkg/loader" "github.com/fugue/regula/pkg/rego" - // "github.com/fugue/regula/pkg/reporter" "github.com/spf13/cobra" - "github.com/thediveo/enumflag" - // "golang.org/x/crypto/ssh/terminal" ) func NewREPLCommand() *cobra.Command { - var inputType loader.InputType - // var format reporter.Format - // severity := reporter.Unknown cmd := &cobra.Command{ Use: "repl", Short: "Evaluate rules against infrastructure-as-code with Regula.", @@ -47,97 +40,22 @@ func NewREPLCommand() *cobra.Command { fmt.Println(err) os.Exit(1) } - // noIgnore, err := cmd.Flags().GetBool("no-ignore") - // if err != nil { - // fmt.Println(err) - // os.Exit(1) - // } ctx := context.TODO() - // repl, err := rego.NewREPL(®o.RuleRunnerOptions{ - // Ctx: ctx, - // UserOnly: userOnly, - // Includes: includes, - // }) - err = rego.NewREPL(®o.RuleRunnerOptions{ + err = rego.REPL(®o.RuleRunnerOptions{ Ctx: ctx, UserOnly: userOnly, Includes: includes, }) - // ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ - // Ctx: ctx, - // UserOnly: userOnly, - // Includes: includes, - // }) if err != nil { fmt.Println(err) os.Exit(1) } - - // repl.Loop(ctx) - - // if len(paths) < 1 { - // if terminal.IsTerminal(int(os.Stdin.Fd())) { - // // Not using os.Getwd here so that we get relative paths. - // // A single dot should mean the same on windows. - // paths = []string{"."} - // } else { - // paths = []string{"-"} - // } - // } - - // loadedFiles, err := loader.LoadPaths(loader.LoadPathsOptions{ - // Paths: paths, - // InputType: inputType, - // NoIgnore: noIgnore, - // }) - // if err != nil { - // fmt.Println(err) - // os.Exit(1) - // } - - // results, err := ruleRunner.Run(loadedFiles.RegulaInput()) - // if err != nil { - // fmt.Println(err) - // os.Exit(1) - // } - - // reporterFunc, _ := reporter.GetReporter(format) - // r := results[0] - // output, err := reporter.ParseRegulaOutput(loadedFiles, r) - // if err != nil { - // fmt.Println(err) - // os.Exit(1) - // } - // report, err := reporterFunc(output) - // if err != nil { - // fmt.Println(err) - // os.Exit(1) - // } - // if report != "" { - // fmt.Println(report) - // } - // if output.ExceedsSeverity(severity) { - // os.Exit(1) - // } }, } cmd.Flags().StringSliceP("include", "i", nil, "Specify additional rego files or directories to include") cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") - // cmd.Flags().BoolP("no-ignore", "n", false, "Disable use of .gitignore") - cmd.Flags().VarP( - enumflag.New(&inputType, "input-type", loader.InputTypeIDs, enumflag.EnumCaseInsensitive), - "input-type", "t", - "Set the input type for the given paths") - // cmd.Flags().VarP( - // enumflag.New(&severity, "severity", reporter.SeverityIds, enumflag.EnumCaseInsensitive), - // "severity", "s", - // "Set the minimum severity that will result in a non-zero exit code.") - // cmd.Flags().VarP( - // enumflag.New(&format, "format", reporter.FormatIds, enumflag.EnumCaseInsensitive), - // "format", "f", - // "Set the output format") return cmd } diff --git a/cmd/test.go b/cmd/test.go new file mode 100644 index 00000000..a59330e0 --- /dev/null +++ b/cmd/test.go @@ -0,0 +1,49 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + _ "embed" + "fmt" + "os" + + "github.com/fugue/regula/pkg/rego" + + "github.com/spf13/cobra" +) + +func NewTestCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "test", + Short: "Run OPA test with Regula.", + Run: func(cmd *cobra.Command, paths []string) { + ctx := context.TODO() + err := rego.RunTest(®o.RunTestOptions{ + Ctx: ctx, + Paths: paths, + }) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + }, + } + return cmd +} + +func init() { + rootCmd.AddCommand(NewTestCommand()) +} diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 2cb6b710..7f7f2d64 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -31,7 +31,7 @@ type LoadPathsOptions struct { func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { configurations := newLoadedConfigurations() - detector, err := detectorByInputType(options.InputType) + detector, err := DetectorByInputType(options.InputType) if err != nil { return nil, err } @@ -176,7 +176,7 @@ func (l *loadedConfigurations) Count() int { return len(l.configurations) } -func detectorByInputType(inputType InputType) (ConfigurationDetector, error) { +func DetectorByInputType(inputType InputType) (ConfigurationDetector, error) { switch inputType { case Auto: return NewAutoDetector( diff --git a/pkg/rego/load.go b/pkg/rego/load.go index 23c4e481..8e067607 100644 --- a/pkg/rego/load.go +++ b/pkg/rego/load.go @@ -1,6 +1,7 @@ package rego import ( + "context" "embed" "encoding/json" "fmt" @@ -11,6 +12,8 @@ import ( "github.com/fugue/regula/pkg/loader" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/storage" + "github.com/open-policy-agent/opa/storage/inmem" "github.com/open-policy-agent/opa/types" ) @@ -20,6 +23,9 @@ var regulaLib embed.FS //go:embed rules var regulaRules embed.FS +//go:embed test_helper.rego +var testHelperRego []byte + var loadExts map[string]bool = map[string]bool{ ".rego": true, ".yaml": true, @@ -62,6 +68,53 @@ func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { return modules, nil } +func loadIncludesAst(includes []string) (map[string]*ast.Module, error) { + modules := map[string]*ast.Module{} + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); !loadExts[ext] { + return nil + } + contents, err := os.ReadFile(path) + if err != nil { + return err + } + module, err := ast.ParseModule(path, string(contents)) + if err != nil { + return err + } + modules[path] = module + return nil + } + for _, path := range includes { + info, err := os.Stat(path) + if err != nil { + return nil, err + } + if info.IsDir() { + if err := filepath.WalkDir(path, walkDirFunc); err != nil { + return nil, err + } + continue + } + contents, err := os.ReadFile(path) + if err != nil { + return nil, err + } + module, err := ast.ParseModule(path, string(contents)) + if err != nil { + return nil, err + } + modules[path] = module + } + return modules, nil +} + func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { modules := []func(r *rego.Rego){} o := osFs{} @@ -110,77 +163,173 @@ func loadRegula(userOnly bool) ([]func(r *rego.Rego), error) { return modules, nil } -func defineLoadFunction() func(r *rego.Rego) { - return rego.Function1( - ®o.Function{ - Name: "regula.load", - Decl: types.NewFunction(types.Args(types.S), types.A), - }, - func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { - if str, ok := a.Value.(ast.String); ok { - path := string(str) - configs, err := loader.LoadPaths(loader.LoadPathsOptions{ - Paths: []string{path}, - InputType: loader.Auto, - NoIgnore: false, - }) - if err != nil { - return nil, err - } - - v, err := ast.InterfaceToValue(configs.RegulaInput()) - if err != nil { - return nil, err - } - - return ast.NewTerm(v), nil - } - return nil, nil - }, - ) +func loadDirectoryRaw(fsys fs.FS, path string) (map[string][]byte, error) { + modules := map[string][]byte{} + walkDirFunc := func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if ext := filepath.Ext(path); !loadExts[ext] { + return nil + } + raw, err := fs.ReadFile(fsys, path) + if err != nil { + return err + } + modules[path] = raw + return nil + } + if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { + return nil, err + } + + return modules, nil } -func defineLoadFunction2() { - rego.RegisterBuiltin1( - ®o.Function{ - Name: "regula.load", - Decl: types.NewFunction(types.Args(types.S), types.A), - Memoize: true, - }, - func(_ rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { - var path string - if err := ast.As(a.Value, &path); err != nil { - return nil, err - } +func loadRegulaRaw(userOnly bool) (map[string][]byte, error) { + libModules, err := loadDirectoryRaw(regulaLib, "lib") + if err != nil { + return nil, err + } + if !userOnly { + rulesModules, err := loadDirectoryRaw(regulaRules, "rules") + if err != nil { + return nil, err + } + for k, v := range rulesModules { + libModules[k] = v + } + } + libModules["test_helper.rego"] = testHelperRego + return libModules, nil +} - configs, err := loader.LoadPaths(loader.LoadPathsOptions{ - Paths: []string{path}, - InputType: loader.Auto, - NoIgnore: false, - }) - if err != nil { - return nil, err - } +func resolvePath(path, location string) string { + if !filepath.IsAbs(path) { + if location == "" { + location = "." + } else { + location = filepath.Dir(location) + } + path = filepath.Join(location, path) + } + return path +} - // testVal := map[string]string{ - // "foo": "bar", - // } - testVal := []interface{}{} +func loadToAstTerm(options loader.LoadPathsOptions) (*ast.Term, error) { + configs, err := loader.LoadPaths(options) + if err != nil { + return nil, err + } - bts, _ := json.Marshal(configs.RegulaInput()) - _ = json.Unmarshal(bts, &testVal) + parseable := []interface{}{} - // v, err := ast.InterfaceToValue(configs.RegulaInput()) - v, err := ast.InterfaceToValue(testVal) - if err != nil { - return nil, err - } + raw, _ := json.Marshal(configs.RegulaInput()) + _ = json.Unmarshal(raw, &parseable) + + v, err := ast.InterfaceToValue(parseable) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil +} + +func regulaLoad(ctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + var path string + if err := ast.As(a.Value, &path); err != nil { + return nil, err + } + + path = resolvePath(path, ctx.Location.File) + + return loadToAstTerm(loader.LoadPathsOptions{ + Paths: []string{path}, + InputType: loader.Auto, + NoIgnore: false, + }) +} + +func regulaLoadType(ctx rego.BuiltinContext, a *ast.Term, b *ast.Term) (*ast.Term, error) { + var path string + var inputTypeStr string + if err := ast.As(a.Value, &path); err != nil { + return nil, err + } + if err := ast.As(b.Value, &inputTypeStr); err != nil { + return nil, err + } + + var inputType loader.InputType + switch inputTypeStr { + case "cfn": + inputType = loader.Cfn + case "tf-plan": + inputType = loader.TfPlan + default: + return nil, fmt.Errorf("Unrecognized input type %v", inputTypeStr) + } + + path = resolvePath(path, ctx.Location.File) - return ast.NewTerm(v), nil + return loadToAstTerm(loader.LoadPathsOptions{ + Paths: []string{path}, + InputType: inputType, + NoIgnore: false, + }) +} + +func defineLoadFunction() { + rego.RegisterBuiltin1( + ®o.Function{ + Name: "regula_load", + Decl: types.NewFunction(types.Args(types.S), types.A), + Memoize: true, + }, + regulaLoad, + ) + rego.RegisterBuiltin2( + ®o.Function{ + Name: "regula_load_type", + Decl: types.NewFunction(types.Args(types.S, types.S), types.A), + Memoize: true, }, + regulaLoadType, ) } +type InitStoreOptions struct { + Ctx context.Context + UserOnly bool +} + +func initStore(options *InitStoreOptions) (storage.Store, error) { + modules, err := loadRegulaRaw(options.UserOnly) + if err != nil { + return nil, err + } + store := inmem.New() + txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ + Write: true, + }) + if err != nil { + return nil, err + } + // defer store.Abort(options.Ctx, txn) + for path, raw := range modules { + if err := store.UpsertPolicy(options.Ctx, txn, path, raw); err != nil { + return nil, err + } + } + if err := store.Commit(options.Ctx, txn); err != nil { + return nil, err + } + return store, nil +} + type osFs struct{} func (o osFs) Open(name string) (fs.File, error) { diff --git a/pkg/rego/repl.go b/pkg/rego/repl.go index d90f86a9..ca8a8d59 100644 --- a/pkg/rego/repl.go +++ b/pkg/rego/repl.go @@ -1,235 +1,20 @@ package rego import ( - _ "embed" - "fmt" - "io/fs" "os" - "path/filepath" - "github.com/open-policy-agent/opa/ast" - "github.com/open-policy-agent/opa/bundle" - "github.com/open-policy-agent/opa/metrics" "github.com/open-policy-agent/opa/repl" - "github.com/open-policy-agent/opa/runtime" - "github.com/open-policy-agent/opa/storage" - "github.com/open-policy-agent/opa/storage/inmem" ) -//go:embed test_helper.rego -var testHelperRego []byte - -func loadDirectoryRaw(fsys fs.FS, path string) (map[string][]byte, error) { - modules := map[string][]byte{} - walkDirFunc := func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if ext := filepath.Ext(path); !loadExts[ext] { - return nil - } - raw, err := fs.ReadFile(fsys, path) - if err != nil { - return err - } - modules[path] = raw - return nil - } - if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { - return nil, err - } - - return modules, nil -} - -func loadAstModule(fsys fs.FS, path string) (*ast.Module, error) { - contents, err := fs.ReadFile(fsys, path) - if err != nil { - return nil, err - } - return ast.ParseModule(path, string(contents)) -} - -func loadDirectoryAst(fsys fs.FS, path string) (map[string]*ast.Module, error) { - modules := map[string]*ast.Module{} - walkDirFunc := func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if ext := filepath.Ext(path); !loadExts[ext] { - return nil - } - module, err := loadAstModule(fsys, path) - if err != nil { - return err - } - modules[path] = module - return nil - } - if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { - return nil, err - } - - return modules, nil -} - -func loadRegulaAst() (map[string]*ast.Module, error) { - libModules, err := loadDirectoryAst(regulaLib, "lib") - if err != nil { - return nil, err - } - rulesModules, err := loadDirectoryAst(regulaRules, "rules") - if err != nil { - return nil, err - } - for k, v := range rulesModules { - libModules[k] = v - } - return libModules, nil -} - -func loadRegulaRaw() (map[string][]byte, error) { - libModules, err := loadDirectoryRaw(regulaLib, "lib") - if err != nil { - return nil, err - } - rulesModules, err := loadDirectoryRaw(regulaRules, "rules") - if err != nil { - return nil, err - } - for k, v := range rulesModules { - libModules[k] = v - } - libModules["test_helper.rego"] = testHelperRego - return libModules, nil -} - -func initStore(options *RuleRunnerOptions) (storage.Store, error) { - modules, err := loadRegulaAst() - if err != nil { - return nil, err - } - - store := inmem.New() - txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ - Write: true, - }) - if err != nil { - return nil, err - } - c := ast.NewCompiler() - // txn := storage.New(options.Ctx, store) - defer store.Abort(options.Ctx, txn) - bundleOpts := &bundle.ActivateOpts{ - Ctx: options.Ctx, - Store: store, - Txn: txn, - Compiler: c, - Metrics: metrics.New(), - Bundles: map[string]*bundle.Bundle{}, - ExtraModules: modules, - } - if err := bundle.Activate(bundleOpts); err != nil { - return nil, err - } - // if err := store.Commit(options.Ctx, txn); err != nil { - // return nil, err - // } - return store, nil -} - -func initStore2(options *RuleRunnerOptions) (storage.Store, error) { - modules, err := loadRegulaRaw() - if err != nil { - return nil, err - } - store := inmem.New() - txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ - Write: true, +func REPL(options *RuleRunnerOptions) error { + defineLoadFunction() + store, err := initStore(&InitStoreOptions{ + Ctx: options.Ctx, }) - if err != nil { - return nil, err - } - // defer store.Abort(options.Ctx, txn) - for path, raw := range modules { - if err := store.UpsertPolicy(options.Ctx, txn, path, raw); err != nil { - return nil, err - } - } - if err := store.Commit(options.Ctx, txn); err != nil { - return nil, err - } - return store, nil -} - -func NewREPL(options *RuleRunnerOptions) error { - defineLoadFunction2() - store, err := initStore2(options) if err != nil { return err } - txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ - Write: false, - }) - defer store.Abort(options.Ctx, txn) - if err != nil { - return err - } - policies, err := store.ListPolicies(options.Ctx, txn) - if err != nil { - return err - } - fmt.Println(policies) - // regula, err := loadRegula(options.UserOnly) - // if err != nil { - // return nil, fmt.Errorf("Failed to load Regula: %v", err) - // } - - // includes, err := loadIncludes(options.Includes) - // if err != nil { - // return nil, fmt.Errorf("Failed to load includes: %v", err) - // } - // regoFuncs := []func(r *rego.Rego){rego.Store(store)} - // regoFuncs = append(regoFuncs, regula...) - // regoFuncs = append(regoFuncs, includes...) - - // // regoFuncs = append(regoFuncs, defineLoadFunction()) - // // regoFuncs = append(regoFuncs, ) - // regoFuncs = append(regoFuncs, rego.Query("data.fugue.regula.report")) - // _, err = rego.New( - // regoFuncs..., - // ).PrepareForPartial(options.Ctx) - // // _, err = query.Compile(options.Ctx, rego.CompilePartial(false)) - // if err != nil { - // return nil, err - // } - - // policies, err := store.ListPolicies(options.Ctx, txn) - // if err != nil { - // return err - // } - // fmt.Println(policies) - - r := repl.New(store, "./.regula-history", os.Stdout, "", 50, "foo") + r := repl.New(store, "./.regula-history", os.Stdout, "", 50, "Regula REPL") r.Loop(options.Ctx) return nil } - -func StartREPL(options *RuleRunnerOptions) error { - defineLoadFunction2() - r, err := runtime.NewRuntime(options.Ctx, runtime.Params{ - Output: os.Stdout, - }) - if err != nil { - return err - } - - r.StartREPL(options.Ctx) - return nil -} diff --git a/pkg/rego/test_helper.rego b/pkg/rego/test_helper.rego index 974557ac..966f82b4 100644 --- a/pkg/rego/test_helper.rego +++ b/pkg/rego/test_helper.rego @@ -2,9 +2,7 @@ package regula import data.fugue.resource_view.resource_view_input -mock_input(path) = ret { - iac_configs := regula.load(path) +mock_input(iac_configs) = ret { iac_config = iac_configs[_] - iac_config.filepath == path ret = resource_view_input with input as iac_config.content } diff --git a/pkg/rego/tester.go b/pkg/rego/tester.go new file mode 100644 index 00000000..c75a54a9 --- /dev/null +++ b/pkg/rego/tester.go @@ -0,0 +1,56 @@ +package rego + +import ( + "context" + "os" + + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/storage/inmem" + "github.com/open-policy-agent/opa/tester" +) + +type RunTestOptions struct { + Ctx context.Context + Paths []string +} + +func RunTest(options *RunTestOptions) error { + defineLoadFunction() + // store, err := initStore(&InitStoreOptions{ + // Ctx: options.Ctx, + // UserOnly: true, + // }) + store := inmem.New() + // if err != nil { + // return err + // } + + modules, err := loadIncludesAst(options.Paths) + if err != nil { + return err + } + + regulaRaw, err := loadRegulaRaw(true) + if err != nil { + return err + } + for k, v := range regulaRaw { + module, err := ast.ParseModule(k, string(v)) + if err != nil { + return err + } + modules[k] = module + } + + ch, err := tester.NewRunner().SetStore(store).EnableTracing(true).Run(options.Ctx, modules) + if err != nil { + return err + } + reporter := tester.PrettyReporter{ + Output: os.Stdout, + FailureLine: true, + Verbose: true, + } + reporter.Report(ch) + return nil +} From ff9e1be8958baad80876560272f4f0a631e75dec Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 3 May 2021 11:48:43 -0400 Subject: [PATCH 31/65] [RM-5352] Some cleanup to repl and test --- Makefile | 4 +- cmd/repl.go | 12 +- cmd/run.go | 21 +- cmd/test.go | 8 +- go.sum | 13 - pkg/loader/base.go | 17 ++ pkg/rego/base.go | 14 + pkg/rego/builtin.go | 100 ++++++++ pkg/rego/load.go | 325 +++++------------------- pkg/rego/repl.go | 20 -- pkg/rego/runner.go | 77 ------ pkg/rego/runrepl.go | 50 ++++ pkg/rego/runrules.go | 66 +++++ pkg/rego/{tester.go => runtest.go} | 38 +-- {pkg/rego => rego/lib}/test_helper.rego | 0 15 files changed, 349 insertions(+), 416 deletions(-) create mode 100644 pkg/rego/base.go create mode 100644 pkg/rego/builtin.go delete mode 100644 pkg/rego/repl.go delete mode 100644 pkg/rego/runner.go create mode 100644 pkg/rego/runrepl.go create mode 100644 pkg/rego/runrules.go rename pkg/rego/{tester.go => runtest.go} (58%) rename {pkg/rego => rego/lib}/test_helper.rego (100%) diff --git a/Makefile b/Makefile index eb4bb74d..dc18b3f3 100644 --- a/Makefile +++ b/Makefile @@ -20,9 +20,11 @@ COPIED_REGO_LIB = pkg/rego/lib COPIED_REGO_RULES = pkg/rego/rules $(COPIED_REGO_LIB): $(REGO_LIB_SOURCE) + rm -rf ./$(COPIED_REGO_LIB) cp -R rego/lib $(COPIED_REGO_LIB) $(COPIED_REGO_RULES): $(REGO_RULES_SOURCE) + rm -rf ./$(COPIED_REGO_RULES) cp -R rego/rules $(COPIED_REGO_RULES) $(GOLINT): @@ -31,7 +33,7 @@ $(GOLINT): $(MOCKGEN): go install github.com/golang/mock/mockgen@v1.5.0 -$(BINARY): $(CLI_SOURCE) $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) pkg/rego/test_helper.rego +$(BINARY): $(CLI_SOURCE) $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) $(CLI_BUILD) -v -o $@ $(BINARY)-linux-amd64: $(SOURCE) diff --git a/cmd/repl.go b/cmd/repl.go index c1027839..a690ca54 100644 --- a/cmd/repl.go +++ b/cmd/repl.go @@ -27,21 +27,16 @@ import ( func NewREPLCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "repl", + Use: "repl [rego paths]", Short: "Evaluate rules against infrastructure-as-code with Regula.", - Run: func(cmd *cobra.Command, paths []string) { - includes, err := cmd.Flags().GetStringSlice("include") - if err != nil { - fmt.Println(err) - os.Exit(1) - } + Run: func(cmd *cobra.Command, includes []string) { userOnly, err := cmd.Flags().GetBool("user-only") if err != nil { fmt.Println(err) os.Exit(1) } ctx := context.TODO() - err = rego.REPL(®o.RuleRunnerOptions{ + err = rego.RunREPL(®o.RunREPLOptions{ Ctx: ctx, UserOnly: userOnly, Includes: includes, @@ -54,7 +49,6 @@ func NewREPLCommand() *cobra.Command { }, } - cmd.Flags().StringSliceP("include", "i", nil, "Specify additional rego files or directories to include") cmd.Flags().BoolP("user-only", "u", false, "Disable built-in rules") return cmd } diff --git a/cmd/run.go b/cmd/run.go index 21d266fd..2ceed054 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -56,17 +56,10 @@ func NewRunCommand() *cobra.Command { os.Exit(1) } ctx := context.TODO() - ruleRunner, err := rego.NewRuleRunner(®o.RuleRunnerOptions{ - Ctx: ctx, - UserOnly: userOnly, - Includes: includes, - }) - if err != nil { fmt.Println(err) os.Exit(1) } - if len(paths) < 1 { if terminal.IsTerminal(int(os.Stdin.Fd())) { // Not using os.Getwd here so that we get relative paths. @@ -86,16 +79,18 @@ func NewRunCommand() *cobra.Command { fmt.Println(err) os.Exit(1) } - - results, err := ruleRunner.Run(loadedFiles.RegulaInput()) + result, err := rego.RunRules(®o.RunRulesOptions{ + Ctx: ctx, + UserOnly: userOnly, + Includes: includes, + Input: loadedFiles.RegulaInput(), + }) + reporterFunc, err := reporter.GetReporter(format) if err != nil { fmt.Println(err) os.Exit(1) } - - reporterFunc, _ := reporter.GetReporter(format) - r := results[0] - output, err := reporter.ParseRegulaOutput(loadedFiles, r) + output, err := reporter.ParseRegulaOutput(loadedFiles, *result) if err != nil { fmt.Println(err) os.Exit(1) diff --git a/cmd/test.go b/cmd/test.go index a59330e0..c65c975a 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -27,13 +27,13 @@ import ( func NewTestCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "test", + Use: "test [rego paths]", Short: "Run OPA test with Regula.", - Run: func(cmd *cobra.Command, paths []string) { + Run: func(cmd *cobra.Command, includes []string) { ctx := context.TODO() err := rego.RunTest(®o.RunTestOptions{ - Ctx: ctx, - Paths: paths, + Ctx: ctx, + Includes: includes, }) if err != nil { fmt.Println(err) diff --git a/go.sum b/go.sum index 14f32714..d1f774cb 100644 --- a/go.sum +++ b/go.sum @@ -40,16 +40,13 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -87,7 +84,6 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -124,7 +120,6 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -132,7 +127,6 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -147,7 +141,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -224,7 +217,6 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -297,14 +289,12 @@ github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -312,7 +302,6 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.14.0 h1:RHRyE8UocrbjU+6UvRzwi6HjiDfxrrBU91TtbKzkGp4= github.com/prometheus/common v0.14.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -320,7 +309,6 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0 h1:wH4vA7pcjKuZzjF7lM8awk4fnuJO6idemZXoKnULUx4= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -568,7 +556,6 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 6a6c9b46..531f3465 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -14,6 +14,8 @@ package loader +import "fmt" + //go:generate mockgen -destination=../mocks/mock_iacconfiguration.go -package=mocks github.com/fugue/regula/pkg/loader IACConfiguration //go:generate mockgen -destination=../mocks/mock_configurationdetector.go -package=mocks github.com/fugue/regula/pkg/loader ConfigurationDetector //go:generate mockgen -destination=../mocks/mock_inputpath.go -package=mocks github.com/fugue/regula/pkg/loader InputPath @@ -46,6 +48,21 @@ var InputTypeIDs = map[InputType][]string{ Cfn: {"cfn"}, } +// InputTypeForString is a utility function to translate the string name of an input +// type to an InputType enum +func InputTypeForString(typeStr string) (InputType, error) { + switch typeStr { + case "auto": + return Auto, nil + case "cfn": + return Cfn, nil + case "tf-plan": + return TfPlan, nil + default: + return -1, fmt.Errorf("Unrecognized input type %v", typeStr) + } +} + // LoadedConfigurations is a container for IACConfigurations loaded by Regula. type LoadedConfigurations interface { // AddConfiguration adds a configuration entry for the given path diff --git a/pkg/rego/base.go b/pkg/rego/base.go new file mode 100644 index 00000000..c5154e94 --- /dev/null +++ b/pkg/rego/base.go @@ -0,0 +1,14 @@ +package rego + +import ( + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" +) + +type RegoFile interface { + Raw() []byte + String() string + AstModule() (*ast.Module, error) + RegoModule() func(r *rego.Rego) + Path() string +} diff --git a/pkg/rego/builtin.go b/pkg/rego/builtin.go new file mode 100644 index 00000000..16ac85c6 --- /dev/null +++ b/pkg/rego/builtin.go @@ -0,0 +1,100 @@ +package rego + +import ( + "encoding/json" + "path/filepath" + + "github.com/fugue/regula/pkg/loader" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/rego" + "github.com/open-policy-agent/opa/types" +) + +func resolvePath(path, location string) string { + if !filepath.IsAbs(path) { + if location == "" { + location = "." + } else { + location = filepath.Dir(location) + } + path = filepath.Join(location, path) + } + return path +} + +func loadToAstTerm(options loader.LoadPathsOptions) (*ast.Term, error) { + configs, err := loader.LoadPaths(options) + if err != nil { + return nil, err + } + + parseable := []interface{}{} + + raw, _ := json.Marshal(configs.RegulaInput()) + _ = json.Unmarshal(raw, &parseable) + + v, err := ast.InterfaceToValue(parseable) + if err != nil { + return nil, err + } + + return ast.NewTerm(v), nil +} + +func regulaLoad(ctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { + var path string + if err := ast.As(a.Value, &path); err != nil { + return nil, err + } + + path = resolvePath(path, ctx.Location.File) + + return loadToAstTerm(loader.LoadPathsOptions{ + Paths: []string{path}, + InputType: loader.Auto, + NoIgnore: false, + }) +} + +func regulaLoadType(ctx rego.BuiltinContext, a *ast.Term, b *ast.Term) (*ast.Term, error) { + var path string + var inputTypeStr string + if err := ast.As(a.Value, &path); err != nil { + return nil, err + } + if err := ast.As(b.Value, &inputTypeStr); err != nil { + return nil, err + } + + inputType, err := loader.InputTypeForString(inputTypeStr) + if err != nil { + return nil, err + } + + path = resolvePath(path, ctx.Location.File) + + return loadToAstTerm(loader.LoadPathsOptions{ + Paths: []string{path}, + InputType: inputType, + NoIgnore: false, + }) +} + +func registerBuiltins() { + rego.RegisterBuiltin1( + ®o.Function{ + Name: "regula_load", + Decl: types.NewFunction(types.Args(types.S), types.A), + Memoize: true, + }, + regulaLoad, + ) + rego.RegisterBuiltin2( + ®o.Function{ + Name: "regula_load_type", + Decl: types.NewFunction(types.Args(types.S, types.S), types.A), + Memoize: true, + }, + regulaLoadType, + ) +} diff --git a/pkg/rego/load.go b/pkg/rego/load.go index 8e067607..64c3b1cb 100644 --- a/pkg/rego/load.go +++ b/pkg/rego/load.go @@ -1,20 +1,13 @@ package rego import ( - "context" "embed" - "encoding/json" - "fmt" "io/fs" "os" "path/filepath" - "github.com/fugue/regula/pkg/loader" "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/rego" - "github.com/open-policy-agent/opa/storage" - "github.com/open-policy-agent/opa/storage/inmem" - "github.com/open-policy-agent/opa/types" ) //go:embed lib @@ -23,9 +16,6 @@ var regulaLib embed.FS //go:embed rules var regulaRules embed.FS -//go:embed test_helper.rego -var testHelperRego []byte - var loadExts map[string]bool = map[string]bool{ ".rego": true, ".yaml": true, @@ -33,16 +23,43 @@ var loadExts map[string]bool = map[string]bool{ ".json": true, } -func loadModule(fsys fs.FS, path string) (func(r *rego.Rego), error) { +type regoFile struct { + path string + contents []byte +} + +func (r *regoFile) Raw() []byte { + return r.contents +} + +func (r *regoFile) String() string { + return string(r.contents) +} + +func (r *regoFile) AstModule() (*ast.Module, error) { + return ast.ParseModule(r.Path(), r.String()) +} + +func (r *regoFile) RegoModule() func(r *rego.Rego) { + return rego.Module(r.Path(), r.String()) +} + +func (r *regoFile) Path() string { + return r.path +} + +func newRegoFile(fsys fs.FS, path string) (RegoFile, error) { contents, err := fs.ReadFile(fsys, path) if err != nil { return nil, err } - return rego.Module(path, string(contents)), nil + return ®oFile{ + path: path, + contents: contents, + }, nil } -func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { - modules := []func(r *rego.Rego){} +func loadDirectory(fsys fs.FS, path string, cb func(r RegoFile) error) error { walkDirFunc := func(path string, d fs.DirEntry, err error) error { if err != nil { return err @@ -53,289 +70,87 @@ func loadDirectory(fsys fs.FS, path string) ([]func(r *rego.Rego), error) { if ext := filepath.Ext(path); !loadExts[ext] { return nil } - module, err := loadModule(fsys, path) + regoFile, err := newRegoFile(fsys, path) if err != nil { return err } - modules = append(modules, module) + if err := cb(regoFile); err != nil { + return err + } return nil } if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { - return nil, err + return err } - return modules, nil + return nil } -func loadIncludesAst(includes []string) (map[string]*ast.Module, error) { - modules := map[string]*ast.Module{} - walkDirFunc := func(path string, d fs.DirEntry, err error) error { - if err != nil { - return err - } - if d.IsDir() { - return nil - } - if ext := filepath.Ext(path); !loadExts[ext] { - return nil - } - contents, err := os.ReadFile(path) - if err != nil { - return err - } - module, err := ast.ParseModule(path, string(contents)) - if err != nil { - return err - } - modules[path] = module - return nil - } - for _, path := range includes { +func loadOsFiles(paths []string, cb func(r RegoFile) error) error { + fsys := &osFs{} + for _, path := range paths { info, err := os.Stat(path) if err != nil { - return nil, err - } - if info.IsDir() { - if err := filepath.WalkDir(path, walkDirFunc); err != nil { - return nil, err - } - continue - } - contents, err := os.ReadFile(path) - if err != nil { - return nil, err - } - module, err := ast.ParseModule(path, string(contents)) - if err != nil { - return nil, err - } - modules[path] = module - } - return modules, nil -} - -func loadIncludes(includes []string) ([]func(r *rego.Rego), error) { - modules := []func(r *rego.Rego){} - o := osFs{} - for _, path := range includes { - info, err := fs.Stat(o, path) - if err != nil { - return nil, err + return err } if info.IsDir() { - dirModules, err := loadDirectory(o, path) + err := loadDirectory(fsys, path, cb) if err != nil { - return nil, err + return err } - - modules = append(modules, dirModules...) continue } - if ext := filepath.Ext(path); !loadExts[ext] { - return nil, fmt.Errorf("Unsupported file type %v in includes: %v", ext, path) - } - module, err := loadModule(o, path) - if err != nil { - return nil, err - } - if module != nil { - modules = append(modules, module) - } - } - - return modules, nil -} - -func loadRegula(userOnly bool) ([]func(r *rego.Rego), error) { - modules, err := loadDirectory(regulaLib, "lib") - if err != nil { - return nil, err - } - - if !userOnly { - rules, err := loadDirectory(regulaRules, "rules") - if err != nil { - return nil, err - } - modules = append(modules, rules...) - } - return modules, nil -} - -func loadDirectoryRaw(fsys fs.FS, path string) (map[string][]byte, error) { - modules := map[string][]byte{} - walkDirFunc := func(path string, d fs.DirEntry, err error) error { + file, err := newRegoFile(fsys, path) if err != nil { return err } - if d.IsDir() { - return nil - } - if ext := filepath.Ext(path); !loadExts[ext] { - return nil - } - raw, err := fs.ReadFile(fsys, path) - if err != nil { + if err := cb(file); err != nil { return err } - modules[path] = raw - return nil - } - if err := fs.WalkDir(fsys, path, walkDirFunc); err != nil { - return nil, err } - - return modules, nil + return nil } -func loadRegulaRaw(userOnly bool) (map[string][]byte, error) { - libModules, err := loadDirectoryRaw(regulaLib, "lib") - if err != nil { - return nil, err +func loadRegula(userOnly bool, cb func(r RegoFile) error) error { + if err := loadDirectory(regulaLib, "lib", cb); err != nil { + return err } if !userOnly { - rulesModules, err := loadDirectoryRaw(regulaRules, "rules") - if err != nil { - return nil, err - } - for k, v := range rulesModules { - libModules[k] = v + if err := loadDirectory(regulaRules, "rules", cb); err != nil { + return err } } - libModules["test_helper.rego"] = testHelperRego - return libModules, nil -} -func resolvePath(path, location string) string { - if !filepath.IsAbs(path) { - if location == "" { - location = "." - } else { - location = filepath.Dir(location) - } - path = filepath.Join(location, path) - } - return path + return nil } -func loadToAstTerm(options loader.LoadPathsOptions) (*ast.Term, error) { - configs, err := loader.LoadPaths(options) - if err != nil { - return nil, err - } - - parseable := []interface{}{} - - raw, _ := json.Marshal(configs.RegulaInput()) - _ = json.Unmarshal(raw, &parseable) - - v, err := ast.InterfaceToValue(parseable) - if err != nil { - return nil, err - } - - return ast.NewTerm(v), nil +// I might be missing something, but it looks like the only fs.FS implementation +// with os methods is os.DirFS, which has behavior that we don't want. +type osFs struct { + fs.FS + fs.GlobFS + fs.ReadDirFS + fs.ReadFileFS + fs.StatFS } -func regulaLoad(ctx rego.BuiltinContext, a *ast.Term) (*ast.Term, error) { - var path string - if err := ast.As(a.Value, &path); err != nil { - return nil, err - } - - path = resolvePath(path, ctx.Location.File) - - return loadToAstTerm(loader.LoadPathsOptions{ - Paths: []string{path}, - InputType: loader.Auto, - NoIgnore: false, - }) +func (o *osFs) Open(name string) (fs.File, error) { + return os.Open(name) } -func regulaLoadType(ctx rego.BuiltinContext, a *ast.Term, b *ast.Term) (*ast.Term, error) { - var path string - var inputTypeStr string - if err := ast.As(a.Value, &path); err != nil { - return nil, err - } - if err := ast.As(b.Value, &inputTypeStr); err != nil { - return nil, err - } - - var inputType loader.InputType - switch inputTypeStr { - case "cfn": - inputType = loader.Cfn - case "tf-plan": - inputType = loader.TfPlan - default: - return nil, fmt.Errorf("Unrecognized input type %v", inputTypeStr) - } - - path = resolvePath(path, ctx.Location.File) - - return loadToAstTerm(loader.LoadPathsOptions{ - Paths: []string{path}, - InputType: inputType, - NoIgnore: false, - }) +func (o *osFs) Glob(pattern string) ([]string, error) { + return filepath.Glob(pattern) } -func defineLoadFunction() { - rego.RegisterBuiltin1( - ®o.Function{ - Name: "regula_load", - Decl: types.NewFunction(types.Args(types.S), types.A), - Memoize: true, - }, - regulaLoad, - ) - rego.RegisterBuiltin2( - ®o.Function{ - Name: "regula_load_type", - Decl: types.NewFunction(types.Args(types.S, types.S), types.A), - Memoize: true, - }, - regulaLoadType, - ) +func (o *osFs) ReadDir(name string) ([]fs.DirEntry, error) { + return os.ReadDir(name) } -type InitStoreOptions struct { - Ctx context.Context - UserOnly bool +func (o *osFs) ReadFile(name string) ([]byte, error) { + return os.ReadFile(name) } -func initStore(options *InitStoreOptions) (storage.Store, error) { - modules, err := loadRegulaRaw(options.UserOnly) - if err != nil { - return nil, err - } - store := inmem.New() - txn, err := store.NewTransaction(options.Ctx, storage.TransactionParams{ - Write: true, - }) - if err != nil { - return nil, err - } - // defer store.Abort(options.Ctx, txn) - for path, raw := range modules { - if err := store.UpsertPolicy(options.Ctx, txn, path, raw); err != nil { - return nil, err - } - } - if err := store.Commit(options.Ctx, txn); err != nil { - return nil, err - } - return store, nil -} - -type osFs struct{} - -func (o osFs) Open(name string) (fs.File, error) { - f, err := os.Open(name) - if err != nil { - return nil, err - } - return f, nil +func (o *osFs) Stat(name string) (fs.FileInfo, error) { + return os.Stat(name) } diff --git a/pkg/rego/repl.go b/pkg/rego/repl.go deleted file mode 100644 index ca8a8d59..00000000 --- a/pkg/rego/repl.go +++ /dev/null @@ -1,20 +0,0 @@ -package rego - -import ( - "os" - - "github.com/open-policy-agent/opa/repl" -) - -func REPL(options *RuleRunnerOptions) error { - defineLoadFunction() - store, err := initStore(&InitStoreOptions{ - Ctx: options.Ctx, - }) - if err != nil { - return err - } - r := repl.New(store, "./.regula-history", os.Stdout, "", 50, "Regula REPL") - r.Loop(options.Ctx) - return nil -} diff --git a/pkg/rego/runner.go b/pkg/rego/runner.go deleted file mode 100644 index efcf4c31..00000000 --- a/pkg/rego/runner.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2021 Fugue, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rego - -import ( - "context" - "fmt" - - "github.com/fugue/regula/pkg/loader" - "github.com/open-policy-agent/opa/rego" -) - -// RuleRunner wraps the logic to load Regula into OPA and evaluate rules -// against an input -type RuleRunner struct { - Ctx context.Context - Query rego.PreparedEvalQuery -} - -// RuleRunnerOptions is a set of options to instantiate a RuleRunner object. -type RuleRunnerOptions struct { - Ctx context.Context - UserOnly bool - Includes []string - Debug bool -} - -// NewRuleRunner instantiates a new RuleRunner -func NewRuleRunner(options *RuleRunnerOptions) (*RuleRunner, error) { - // paths := append(options.RulesDirs, options.LibraryDir) - regula, err := loadRegula(options.UserOnly) - if err != nil { - return nil, fmt.Errorf("Failed to load Regula: %v", err) - } - - includes, err := loadIncludes(options.Includes) - if err != nil { - return nil, fmt.Errorf("Failed to load includes: %v", err) - } - regoFuncs := append(regula, includes...) - regoFuncs = append(regoFuncs, rego.Query("data.fugue.regula.report")) - query, err := rego.New( - regoFuncs..., - ).PrepareForEval(options.Ctx) - - if err != nil { - return nil, fmt.Errorf("Failed to initialize OPA: %v", err) - } - - return &RuleRunner{ - Ctx: options.Ctx, - Query: query, - }, nil -} - -// Run evaluates rules against an input. -func (r *RuleRunner) Run(input []loader.RegulaInput) (rego.ResultSet, error) { - results, err := r.Query.Eval(r.Ctx, rego.EvalInput(input)) - - if err != nil { - return nil, fmt.Errorf("Failed to evaluate against input: %v", err) - } - - return results, nil -} diff --git a/pkg/rego/runrepl.go b/pkg/rego/runrepl.go new file mode 100644 index 00000000..fe9cb133 --- /dev/null +++ b/pkg/rego/runrepl.go @@ -0,0 +1,50 @@ +package rego + +import ( + "context" + "os" + + "github.com/open-policy-agent/opa/repl" + "github.com/open-policy-agent/opa/storage" + "github.com/open-policy-agent/opa/storage/inmem" +) + +type RunREPLOptions struct { + Ctx context.Context + UserOnly bool + Includes []string +} + +func RunREPL(options *RunREPLOptions) error { + registerBuiltins() + store, err := initStore(options.Ctx, options.UserOnly, options.Includes) + if err != nil { + return err + } + r := repl.New(store, "./.regula-history", os.Stdout, "", 50, "Regula REPL") + r.Loop(options.Ctx) + return nil +} + +func initStore(ctx context.Context, userOnly bool, includes []string) (storage.Store, error) { + store := inmem.New() + txn, err := store.NewTransaction(ctx, storage.TransactionParams{ + Write: true, + }) + if err != nil { + return nil, err + } + cb := func(r RegoFile) error { + return store.UpsertPolicy(ctx, txn, r.Path(), r.Raw()) + } + if err := loadRegula(userOnly, cb); err != nil { + return nil, err + } + if err := loadOsFiles(includes, cb); err != nil { + return nil, err + } + if err := store.Commit(ctx, txn); err != nil { + return nil, err + } + return store, nil +} diff --git a/pkg/rego/runrules.go b/pkg/rego/runrules.go new file mode 100644 index 00000000..f21f9c2a --- /dev/null +++ b/pkg/rego/runrules.go @@ -0,0 +1,66 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rego + +import ( + "context" + "fmt" + + "github.com/fugue/regula/pkg/loader" + "github.com/open-policy-agent/opa/rego" +) + +// RunRulesOptions is the set of options for RunRules +type RunRulesOptions struct { + Ctx context.Context + UserOnly bool + Includes []string + Input []loader.RegulaInput +} + +// RunRules runs regula and user-specified rules on loaded inputs +func RunRules(options *RunRulesOptions) (*rego.Result, error) { + registerBuiltins() + query, err := prepare(options.Ctx, options.UserOnly, options.Includes) + if err != nil { + return nil, err + } + results, err := query.Eval(options.Ctx, rego.EvalInput(options.Input)) + if err != nil { + return nil, err + } + return &results[0], nil +} + +func prepare(ctx context.Context, userOnly bool, includes []string) (*rego.PreparedEvalQuery, error) { + regoFuncs := []func(r *rego.Rego){ + rego.Query("data.fugue.regula.report"), + } + cb := func(r RegoFile) error { + regoFuncs = append(regoFuncs, rego.Module(r.Path(), r.String())) + return nil + } + if err := loadRegula(userOnly, cb); err != nil { + return nil, err + } + if err := loadOsFiles(includes, cb); err != nil { + return nil, err + } + query, err := rego.New(regoFuncs...).PrepareForEval(ctx) + if err != nil { + return nil, fmt.Errorf("Failed to initialize OPA: %v", err) + } + return &query, nil +} diff --git a/pkg/rego/tester.go b/pkg/rego/runtest.go similarity index 58% rename from pkg/rego/tester.go rename to pkg/rego/runtest.go index c75a54a9..046d497d 100644 --- a/pkg/rego/tester.go +++ b/pkg/rego/runtest.go @@ -10,38 +10,28 @@ import ( ) type RunTestOptions struct { - Ctx context.Context - Paths []string + Ctx context.Context + Includes []string } func RunTest(options *RunTestOptions) error { - defineLoadFunction() - // store, err := initStore(&InitStoreOptions{ - // Ctx: options.Ctx, - // UserOnly: true, - // }) + registerBuiltins() store := inmem.New() - // if err != nil { - // return err - // } - - modules, err := loadIncludesAst(options.Paths) - if err != nil { - return err - } - - regulaRaw, err := loadRegulaRaw(true) - if err != nil { - return err - } - for k, v := range regulaRaw { - module, err := ast.ParseModule(k, string(v)) + modules := map[string]*ast.Module{} + cb := func(r RegoFile) error { + module, err := r.AstModule() if err != nil { return err } - modules[k] = module + modules[r.Path()] = module + return nil + } + if err := loadRegula(true, cb); err != nil { + return err + } + if err := loadOsFiles(options.Includes, cb); err != nil { + return err } - ch, err := tester.NewRunner().SetStore(store).EnableTracing(true).Run(options.Ctx, modules) if err != nil { return err diff --git a/pkg/rego/test_helper.rego b/rego/lib/test_helper.rego similarity index 100% rename from pkg/rego/test_helper.rego rename to rego/lib/test_helper.rego From 6fbfd87d88d49875f6ffacdfd1b31f5b9c8169bd Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 3 May 2021 15:25:44 -0400 Subject: [PATCH 32/65] [RM-5352] Update rule tests to use regula_load function --- .gitattributes | 1 + pkg/loader/cfn.go | 6 + pkg/rego/load.go | 9 +- rego/scripts/generate-test-inputs.sh | 149 +- ...alid_classic_custom_domain_name_infra.rego | 36 +- ..._classic_custom_domain_name_sam_infra.rego | 43 +- .../invalid_v2_custom_domain_name_infra.rego | 57 +- ...valid_v2_custom_domain_name_sam_infra.rego | 41 +- ...alid_classic_custom_domain_name_infra.rego | 29 +- ..._classic_custom_domain_name_sam_infra.rego | 39 +- .../valid_v2_custom_domain_name_infra.rego | 33 +- ...valid_v2_custom_domain_name_sam_infra.rego | 35 +- .../inputs/empty_template_infra.rego | 20 +- .../inputs/invalid_cloudwatch_infra.rego | 82 +- .../invalid_cloudwatch_with_valid_infra.rego | 151 +- .../inputs/invalid_encryption_infra.rego | 82 +- .../inputs/invalid_log_validation_infra.rego | 82 +- ...valid_log_validation_with_valid_infra.rego | 95 +- .../invalid_s3_access_logging_infra.rego | 83 +- .../inputs/invalid_target_public_infra.rego | 87 +- .../invalid_target_public_write_infra.rego | 87 +- .../inputs/valid_cloudwatch_infra.rego | 139 +- .../inputs/valid_encryption_infra.rego | 116 +- .../inputs/valid_log_validation_infra.rego | 83 +- .../inputs/valid_s3_access_logging_infra.rego | 97 +- .../inputs/valid_target_full_check_infra.rego | 153 +- .../cloudtrail/inputs/valid_target_infra.rego | 86 +- .../ebs/inputs/volume_encryption_infra.rego | 55 +- .../cfn/iam/inputs/admin_policy_infra.rego | 265 +-- .../rules/cfn/iam/inputs/policy_infra.rego | 94 +- .../cfn/kms/inputs/key_rotation_infra.rego | 82 +- .../lambda/inputs/empty_template_infra.rego | 20 +- .../invalid_function_not_public_infra.rego | 230 +-- ...invalid_function_not_public_sam_infra.rego | 43 +- ..._function_not_public_with_valid_infra.rego | 84 +- ...n_not_public_account_permission_infra.rego | 73 +- ...t_public_account_permission_sam_infra.rego | 45 +- .../valid_function_not_public_infra.rego | 58 +- .../valid_function_not_public_sam_infra.rego | 30 +- ...n_not_public_service_permission_infra.rego | 71 +- ...t_public_service_permission_sam_infra.rego | 43 +- .../cfn/s3/inputs/empty_template_infra.rego | 20 +- .../invalid_block_public_access_infra.rego | 38 +- ..._s3_data_logging_all_one_bucket_infra.rego | 98 +- ...trail_s3_data_logging_no_trails_infra.rego | 30 +- ...s3_data_logging_read_one_bucket_infra.rego | 98 +- ...ta_logging_trail_no_data_events_infra.rego | 94 +- ..._data_logging_trail_no_selector_infra.rego | 89 +- ...3_data_logging_write_one_bucket_infra.rego | 98 +- ...gging_write_one_bucket_read_all_infra.rego | 115 +- .../invalid_encryption_missing_infra.rego | 24 +- .../invalid_encryption_with_valid_infra.rego | 65 +- ...alid_https_access_bucket_policy_infra.rego | 92 +- .../cfn/s3/inputs/invalid_missing_infra.rego | 49 +- .../valid_block_public_access_infra.rego | 33 +- ...s3_data_logging_all_all_buckets_infra.rego | 102 +- ...s3_data_logging_all_two_buckets_infra.rego | 101 +- ...3_data_logging_read_all_buckets_infra.rego | 102 +- ..._data_logging_write_all_buckets_infra.rego | 102 +- ...gging_write_one_bucket_read_all_infra.rego | 115 +- .../cfn/s3/inputs/valid_encryption_infra.rego | 62 +- ...alid_https_access_bucket_policy_infra.rego | 97 +- .../inputs/default_security_group_infra.rego | 61 +- .../inputs/flow_logging_enabled_infra.rego | 42 +- .../cfn/vpc/inputs/ingress_22_infra.rego | 160 +- .../cfn/vpc/inputs/ingress_3389_infra.rego | 59 +- .../cfn/vpc/inputs/nacl_ingress_22_infra.rego | 221 +-- .../vpc/inputs/nacl_ingress_3389_infra.rego | 221 +-- .../inputs/distribution_https_infra.rego | 1173 +----------- .../inputs/distribution_https_infra.tfplan | 1169 ++++++++++++ .../inputs/log_file_validation_infra.rego | 386 +--- .../inputs/log_file_validation_infra.tfplan | 378 ++++ .../ebs/inputs/volume_encrypted_infra.rego | 233 +-- .../ebs/inputs/volume_encrypted_infra.tfplan | 222 +++ .../tf/aws/iam/inputs/admin_policy_infra.rego | 677 +------ .../aws/iam/inputs/admin_policy_infra.tfplan | 660 +++++++ .../inputs/user_attached_policy_infra.rego | 1567 +--------------- .../inputs/user_attached_policy_infra.tfplan | 1583 +++++++++++++++++ .../tf/aws/kms/inputs/key_rotation_infra.rego | 226 +-- .../aws/kms/inputs/key_rotation_infra.tfplan | 209 +++ .../tf/aws/s3/inputs/bucket_sse_infra.rego | 462 +---- .../tf/aws/s3/inputs/bucket_sse_infra.tfplan | 453 +++++ .../inputs/ingress_anywhere_infra.rego | 530 +----- .../inputs/ingress_anywhere_infra.tfplan | 513 ++++++ .../inputs/ingress_anywhere_rdp_infra.rego | 433 +---- .../inputs/ingress_anywhere_rdp_infra.tfplan | 416 +++++ .../inputs/ingress_anywhere_ssh_infra.rego | 433 +---- .../inputs/ingress_anywhere_ssh_infra.tfplan | 416 +++++ .../tf/aws/vpc/inputs/flow_log_infra.rego | 418 +---- .../tf/aws/vpc/inputs/flow_log_infra.tfplan | 408 +++++ .../security_group_no_inbound_22_infra.rego | 1236 +------------ .../security_group_no_inbound_22_infra.tfplan | 1219 +++++++++++++ .../security_group_no_inbound_3389_infra.rego | 1236 +------------ ...ecurity_group_no_inbound_3389_infra.tfplan | 1219 +++++++++++++ .../inputs/firewall_no_inbound_all_infra.rego | 523 +----- .../firewall_no_inbound_all_infra.tfplan | 506 ++++++ .../inputs/account_deny_access_infra.rego | 856 +-------- .../inputs/account_deny_access_infra.tfplan | 853 +++++++++ .../account_microsoft_services_infra.rego | 936 +--------- .../account_microsoft_services_infra.tfplan | 933 ++++++++++ .../inputs/account_secure_transfer_infra.rego | 511 +----- .../account_secure_transfer_infra.tfplan | 500 ++++++ .../container_private_access_infra.rego | 412 +---- .../container_private_access_infra.tfplan | 397 +++++ .../inputs/firewall_no_ingress_22_infra.rego | 677 +------ .../firewall_no_ingress_22_infra.tfplan | 661 +++++++ .../firewall_no_ingress_3389_infra.rego | 665 +------ .../firewall_no_ingress_3389_infra.tfplan | 649 +++++++ .../inputs/subnet_flow_log_enabled_infra.rego | 377 +--- .../subnet_flow_log_enabled_infra.tfplan | 364 ++++ .../subnet_private_google_access_infra.rego | 321 +--- .../subnet_private_google_access_infra.tfplan | 308 ++++ .../kms/inputs/cryptokey_rotate_infra.rego | 270 +-- .../kms/inputs/cryptokey_rotate_infra.tfplan | 259 +++ 114 files changed, 15124 insertions(+), 19231 deletions(-) create mode 100644 rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tfplan create mode 100644 rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.tfplan create mode 100644 rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tfplan create mode 100644 rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tfplan create mode 100644 rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tfplan create mode 100644 rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tfplan create mode 100644 rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tfplan create mode 100644 rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tfplan create mode 100644 rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tfplan create mode 100644 rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tfplan create mode 100644 rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tfplan create mode 100644 rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tfplan create mode 100644 rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tfplan create mode 100644 rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tfplan diff --git a/.gitattributes b/.gitattributes index e05fe016..d3cada21 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.cfn linguist-language=YAML +*.tfplan linguist-language=JSON diff --git a/pkg/loader/cfn.go b/pkg/loader/cfn.go index 0a0499bc..cc4e5ace 100644 --- a/pkg/loader/cfn.go +++ b/pkg/loader/cfn.go @@ -184,6 +184,12 @@ func decodeIntrinsic(node *yaml.Node, name string) (map[string]interface{}, erro if err := node.Decode(&val); err != nil { return nil, fmt.Errorf("Failed to decode intrinsic: %v", err) } + // Special case for GetAtt + if name == "Fn::GetAtt" { + if valString, ok := val.(string); ok { + val = strings.Split(valString, ".") + } + } intrinsic[name] = val } diff --git a/pkg/rego/load.go b/pkg/rego/load.go index 64c3b1cb..acf77dba 100644 --- a/pkg/rego/load.go +++ b/pkg/rego/load.go @@ -18,9 +18,12 @@ var regulaRules embed.FS var loadExts map[string]bool = map[string]bool{ ".rego": true, - ".yaml": true, - ".yml": true, - ".json": true, + // TODO: We should evaluate how useful it is for end-users to load non-rego files + // in their rules. We'll need to change how these files get loaded into OPA in + // order to support these other extensions. + // ".yaml": true, + // ".yml": true, + // ".json": true, } type regoFile struct { diff --git a/rego/scripts/generate-test-inputs.sh b/rego/scripts/generate-test-inputs.sh index 7498e1ce..c164b533 100755 --- a/rego/scripts/generate-test-inputs.sh +++ b/rego/scripts/generate-test-inputs.sh @@ -17,69 +17,114 @@ set -o nounset -o errexit -o pipefail # Allow overriding terraform version. TERRAFORM="${TERRAFORM:-terraform}" +function output_rego_file { + local infra_file="$1" + local rego_file="$2" + cat < "${rego_file}" +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This package was automatically generated from: +# +# ${infra_file} +# +# using 'generate_test_inputs.sh' and should not be modified +# directly. +# +EOF + cat - >> "${rego_file}" +} + +# the .rego files generated by generate_tf_input and generate_cfn_input look pretty +# similar right now, but we'll want to change the .rego file for tf inputs after we've +# added HCL support. +function generate_tf_input { + local infra_file="$1" + local rego_file="$2" + local rego_dir=$(dirname "${rego_file}") + local rego_basename=$(basename "${rego_file}" .rego) + local plan_json_basename="${rego_basename}.tfplan" + local plan_json="${rego_dir}/${plan_json_basename}" + local workdir="$(mktemp -d)" + trap "rm -rf "$workdir"" return + + cp "${infra_file}" "${workdir}" + + (cd "${workdir}" && + ${TERRAFORM} init && + ${TERRAFORM} plan -refresh=false -out="plan.tfplan" && + ${TERRAFORM} show -json "plan.tfplan" | jq > "${plan_json_basename}") + + cp "${workdir}/${plan_json_basename}" "${plan_json}" + + local package="$(echo "${rego_dir}.${rego_basename}" | tr '/' '.')" + cat <&2 echo "Usage: $0 INFRA_FILE REGO_FILE" exit 1 fi - local workdir="$(mktemp -d)" - trap "rm -rf "$workdir"" return - cp "$1" "$workdir" - - # Switch based on extension. The inner branches must generate - # `$workdir/infra.json` which will be spliced into the rego file. - local extension="${1##*.}" - if [[ "$extension" == "tf" ]]; then - # For some reason running this from the current directory sometimes fails; we - # create a subshell and `cd` to where we copied the terraform configuration. - (cd "$workdir" && - $TERRAFORM init && - $TERRAFORM plan -refresh=false -out="plan.tfplan" && - $TERRAFORM show -json "plan.tfplan" >"infra.json") - elif [[ "$extension" == "cfn" ]]; then - # Convert to standard JSON format using `cfn-flip`. - cfn-flip -j "$1" >"$workdir/infra.json" + local infra_file="$1" + local rego_file="$2" + local extension="${infra_file##*.}" + if [[ "${extension}" == "tf" ]]; then + generate_tf_input "${infra_file}" "${rego_file}" + elif [[ "${extension}" == "cfn" ]]; then + generate_cfn_input "${infra_file}" "${rego_file}" else 1>&2 echo "Unknown extension: $extension" exit 1 fi - local package="$(echo "$(dirname "$2")"/"$(basename "$2" .rego)" | tr '/' '.')" - echo '# Copyright 2020-2021 Fugue, Inc.' >"$2" - echo '#' >>"$2" - echo '# Licensed under the Apache License, Version 2.0 (the "License");' >>"$2" - echo '# you may not use this file except in compliance with the License.' >>"$2" - echo '# You may obtain a copy of the License at' >>"$2" - echo '#' >>"$2" - echo '# http://www.apache.org/licenses/LICENSE-2.0' >>"$2" - echo '#' >>"$2" - echo '# Unless required by applicable law or agreed to in writing, software' >>"$2" - echo '# distributed under the License is distributed on an "AS IS" BASIS,' >>"$2" - echo '# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.' >>"$2" - echo '# See the License for the specific language governing permissions and' >>"$2" - echo '# limitations under the License.' >>"$2" - echo '' >>"$2" - echo '# This package was automatically generated from:' >>"$2" - echo '#' >>"$2" - echo "# $1" >>"$2" - echo '#' >>"$2" - echo '# using `generate_test_inputs.sh` and should not be modified' >>"$2" - echo '# directly.' >>"$2" - echo '#' >>"$2" - echo '# It provides three inputs for testing:' >>"$2" - echo '# - mock_input: The resource view input as passed to advanced rules' >>"$2" - echo '# - mock_resources: The resources present as a convenience for tests' >>"$2" - echo '# - mock_plan_input: The original plan input as generated by terraform' >>"$2" - echo "package $package" >>"$2" - echo "import data.fugue.resource_view.resource_view_input" >>"$2" - echo "mock_input = ret {" >>"$2" - echo " ret = resource_view_input with input as mock_plan_input" >>"$2" - echo "}" >>"$2" - echo "mock_resources = mock_input.resources" >>"$2" - echo "mock_plan_input = $(jq '.' <"$workdir/infra.json")" >>"$2" - - 1>&2 echo "Generated $2" + 1>&2 echo "Generated ${rego_file}" } if [[ $# -eq 0 ]]; then diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego index f5405028..46848ff2 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego @@ -16,37 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_classic_custom_domain_name_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid classic custom domain name configurations", - "Resources": { - "CustomDomainName": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "DomainName": "api.example.com", - "SecurityPolicy": "TLS_1_0", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9bb7fd90-00cf-4326-ae14-7dc62c92dfe5" - } - }, - "CustomDomainName2": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "DomainName": "api-2.example.com", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/cf9e8763-2af9-490f-84ea-c91c0f668755" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_classic_custom_domain_name_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego index 2bf9659b..a0803da1 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego @@ -16,44 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_classic_custom_domain_name_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Invalid classic custom domain name configurations", - "Resources": { - "ServerlessAPI": { - "Type": "AWS::Serverless::Api", - "Properties": { - "StageName": "Prod", - "Domain": { - "DomainName": "api.example.com", - "SecurityPolicy": "TLS_1_0", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9bb7fd90-00cf-4326-ae14-7dc62c92dfe5" - } - } - }, - "ServerlessAPI2": { - "Type": "AWS::Serverless::Api", - "Properties": { - "StageName": "Prod", - "Domain": { - "DomainName": "api-2.example.com", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/cf9e8763-2af9-490f-84ea-c91c0f668755" - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_classic_custom_domain_name_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego index aa4d3c2c..f35649b2 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego @@ -16,58 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_v2_custom_domain_name_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid V2 custom domain name configurations", - "Resources": { - "CustomDomainName": { - "Type": "AWS::ApiGatewayV2::DomainName", - "Properties": { - "DomainName": "api.example.com", - "DomainNameConfigurations": [ - { - "SecurityPolicy": "TLS_1_0", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9bb7fd90-00cf-4326-ae14-7dc62c92dfe5" - } - ] - } - }, - "CustomDomainName2": { - "Type": "AWS::ApiGatewayV2::DomainName", - "Properties": { - "DomainName": "api-2.example.com", - "DomainNameConfigurations": [ - { - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/cf9e8763-2af9-490f-84ea-c91c0f668755" - } - ] - } - }, - "CustomDomainName3": { - "Type": "AWS::ApiGatewayV2::DomainName", - "Properties": { - "DomainName": "api-2.example.com", - "DomainNameConfigurations": [] - } - }, - "CustomDomainName4": { - "Type": "AWS::ApiGatewayV2::DomainName", - "Properties": { - "DomainName": "api-2.example.com" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_v2_custom_domain_name_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego index 4ebf436d..3c91f9d9 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego @@ -16,42 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_v2_custom_domain_name_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Invalid V2 custom domain name configurations", - "Resources": { - "ServerlessAPI": { - "Type": "AWS::Serverless::HttpApi", - "Properties": { - "Domain": { - "DomainName": "api.example.com", - "SecurityPolicy": "TLS_1_0", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9bb7fd90-00cf-4326-ae14-7dc62c92dfe5" - } - } - }, - "ServerlessAPI2": { - "Type": "AWS::Serverless::HttpApi", - "Properties": { - "Domain": { - "DomainName": "api-2.example.com", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/cf9e8763-2af9-490f-84ea-c91c0f668755" - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_v2_custom_domain_name_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego index 1ce3ae9b..01bc7f32 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego @@ -16,30 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_classic_custom_domain_name_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid classic custom domain name configuration", - "Resources": { - "CustomDomainName": { - "Type": "AWS::ApiGateway::DomainName", - "Properties": { - "DomainName": "api.example.com", - "SecurityPolicy": "TLS_1_2", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/fb1b9770-a305-495d-aefb-27e5e101ff3" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_classic_custom_domain_name_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego index 8cd52d46..e001d5ad 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego @@ -16,40 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_classic_custom_domain_name_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Valid classic custom domain name configuration", - "Resources": { - "ServerlessAPI": { - "Type": "AWS::Serverless::Api", - "Properties": { - "StageName": "Prod", - "Domain": { - "DomainName": "api.example.com", - "SecurityPolicy": "TLS_1_2", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9bb7fd90-00cf-4326-ae14-7dc62c92dfe5" - } - } - }, - "ServerlessAPI2": { - "Type": "AWS::Serverless::Api", - "Properties": { - "StageName": "Prod" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_classic_custom_domain_name_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego index 3a182f97..6a8dfdfc 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego @@ -16,34 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_v2_custom_domain_name_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid V2 custom domain name configuration", - "Resources": { - "CustomDomainName": { - "Type": "AWS::ApiGatewayV2::DomainName", - "Properties": { - "DomainName": "api.example.com", - "DomainNameConfigurations": [ - { - "SecurityPolicy": "TLS_1_2", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/fb1b9770-a305-495d-aefb-27e5e101ff3" - } - ] - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_v2_custom_domain_name_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego index 078d37eb..5f994100 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego @@ -16,36 +16,17 @@ # # tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_v2_custom_domain_name_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Valid V2 custom domain name configuration", - "Resources": { - "ServerlessAPI": { - "Type": "AWS::Serverless::HttpApi", - "Properties": { - "Domain": { - "DomainName": "api.example.com", - "SecurityPolicy": "TLS_1_2", - "CertificateArn": "arn:aws:acm:us-east-1:111122223333:certificate/9bb7fd90-00cf-4326-ae14-7dc62c92dfe5" - } - } - }, - "ServerlessAPI2": { - "Type": "AWS::Serverless::HttpApi" - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_v2_custom_domain_name_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego index c5434817..ff0e20a6 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego @@ -16,21 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/empty_template_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.empty_template_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Empty template for testing", - "Resources": {} -} + +import data.regula + +mock_config := regula_load_type("empty_template_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego index 5a80cff8..c1d9f25d 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego @@ -16,83 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_cloudwatch_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail cloudwatch integration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudwatch_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego index 16ead695..9e5dadc4 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego @@ -16,152 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_cloudwatch_with_valid_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid and invalid CloudTrail cloudwatch integration", - "Resources": { - "ValidCloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "valid-ct-prefix", - "TrailName": "cf-valid-trail", - "CloudWatchLogsLogGroupArn": { - "Fn::GetAtt": [ - "CloudTrailLogGroup", - "Arn" - ] - }, - "CloudWatchLogsRoleArn": { - "Fn::GetAtt": [ - "CloudTrailCloudWatchRole", - "Arn" - ] - } - } - }, - "InvalidCloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "invalid-ct-prefix", - "TrailName": "cf-invalid-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "CloudTrailLogGroup": { - "Type": "AWS::Logs::LogGroup" - }, - "CloudTrailCloudWatchRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ - "cloudtrail.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - } - ] - }, - "Path": "/", - "Policies": [ - { - "PolicyName": "watch-policy", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "logs:CreateLogStream", - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": "logs:PutLogEvents", - "Resource": "*" - } - ] - } - } - ] - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudwatch_with_valid_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego index 900f39f0..3cb1245b 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego @@ -16,83 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_encryption_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail encryption configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": false, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_encryption_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego index 557da652..67e289d4 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego @@ -16,83 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_log_validation_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail log file validation", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_log_validation_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego index 642794ac..4494d164 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego @@ -16,96 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_log_validation_with_valid_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid and invalid CloudTrail log file validation", - "Resources": { - "InvalidCloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "invalid-ct-prefix", - "TrailName": "cf-invalid-trail" - } - }, - "ValidCloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "valid-ct-prefix", - "TrailName": "cf-valid-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_log_validation_with_valid_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego index 4201749d..0c218e0b 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego @@ -16,84 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_s3_access_logging_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail S3 access logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_s3_access_logging_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego index 162ccf75..a5eba5bf 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego @@ -16,88 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_target_public_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail target configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "PublicRead", - "BucketName": "cf-fuguetest-bucket" - } - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_target_public_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego index 6e6d825c..6a3896b7 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego @@ -16,88 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_target_public_write_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail target configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "PublicReadWrite", - "BucketName": "cf-fuguetest-bucket" - } - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_target_public_write_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego index 4390682f..d0a8b893 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego @@ -16,140 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_cloudwatch_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail cloudwatch integration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail", - "CloudWatchLogsLogGroupArn": { - "Fn::GetAtt": [ - "CloudTrailLogGroup", - "Arn" - ] - }, - "CloudWatchLogsRoleArn": { - "Fn::GetAtt": [ - "CloudTrailCloudWatchRole", - "Arn" - ] - } - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "CloudTrailLogGroup": { - "Type": "AWS::Logs::LogGroup" - }, - "CloudTrailCloudWatchRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ - "cloudtrail.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - } - ] - }, - "Path": "/", - "Policies": [ - { - "PolicyName": "watch-policy", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "logs:CreateLogStream", - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": "logs:PutLogEvents", - "Resource": "*" - } - ] - } - } - ] - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_cloudwatch_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego index 1755a0d6..c3cd3bb5 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego @@ -16,117 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_encryption_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail encryption configuration", - "Resources": { - "KMSKey": { - "Type": "AWS::KMS::Key", - "Properties": { - "Description": "This key is used to encrypt cloudtrail logs", - "KeyPolicy": { - "Version": "2012-10-17", - "Id": "cloudtrail-key-policy", - "Statement": [ - { - "Sid": "Allow CloudTrail to encrypt logs", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "kms:GenerateDataKey*", - "Resource": "*", - "Condition": { - "StringLike": { - "kms:EncryptionContext:aws:cloudtrail:arn": [ - { - "Fn::Sub": "arn:aws:cloudtrail:*:${AWS::AccountId}:trail/*" - } - ] - } - } - } - ] - }, - "PendingWindowInDays": 10 - } - }, - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IncludeGlobalServiceEvents": false, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail", - "KMSKeyId": { - "Ref": "KMSKey" - } - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_encryption_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego index ed19269e..ff2bd881 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego @@ -16,84 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_log_validation_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail log file validation", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_log_validation_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego index 259813a0..b156dc45 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego @@ -16,98 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_s3_access_logging_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail S3 access logging configuration", - "Resources": { - "AccessLogsBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "LogDeliveryWrite" - } - }, - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "LoggingConfiguration": { - "DestinationBucketName": { - "Ref": "AccessLogsBucket" - }, - "LogFilePrefix": "log/" - } - } - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_s3_access_logging_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego index af5e49d9..dfe6e163 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego @@ -16,154 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_target_full_check_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail target configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "AuthenticatedRead", - "BucketName": "cf-fuguetest-bucket" - } - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "CloudTrailLogging2": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket2" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail2" - } - }, - "LoggingBucket2": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "BucketOwnerFullControl", - "BucketName": "cf-fuguetest-bucket2" - } - }, - "LoggingBucketPolicy2": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket2" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket2", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket2.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_target_full_check_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego index 773864f3..90a1001b 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego @@ -16,87 +16,17 @@ # # tests/rules/cfn/cloudtrail/inputs/valid_target_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_target_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail target configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "EnableLogFileValidation": true, - "IncludeGlobalServiceEvents": true, - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "S3KeyPrefix": "prefix", - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketName": "cf-fuguetest-bucket" - } - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_target_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego b/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego index 6b1b04d9..79046ec2 100644 --- a/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego +++ b/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego @@ -16,56 +16,17 @@ # # tests/rules/cfn/ebs/inputs/volume_encryption_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.ebs.inputs.volume_encryption_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Parameters": { - "AvailabilityZone": { - "Type": "String", - "Default": "us-east-1b" - } - }, - "Resources": { - "ValidVolume01": { - "Type": "AWS::EC2::Volume", - "Properties": { - "AvailabilityZone": { - "Ref": "AvailabilityZone" - }, - "Encrypted": true, - "Size": 1 - } - }, - "InvalidVolume01": { - "Type": "AWS::EC2::Volume", - "Properties": { - "AvailabilityZone": { - "Ref": "AvailabilityZone" - }, - "Encrypted": false, - "Size": 1 - } - }, - "InvalidVolume02": { - "Type": "AWS::EC2::Volume", - "Properties": { - "AvailabilityZone": { - "Ref": "AvailabilityZone" - }, - "Size": 1 - } - } - } -} + +import data.regula + +mock_config := regula_load_type("volume_encryption_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego b/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego index 3274030e..f640cc5f 100644 --- a/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego +++ b/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego @@ -16,266 +16,17 @@ # # tests/rules/cfn/iam/inputs/admin_policy_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.iam.inputs.admin_policy_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "ValidRole01": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ - "ec2.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - } - ] - } - } - }, - "ValidPolicy01": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Roles": [ - { - "Ref": "ValidRole01" - } - ], - "PolicyName": "valid_policy_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": [ - "ec2:StartInstances" - ], - "Resource": [ - "*" - ] - } - } - } - }, - "ValidPolicy02": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Roles": [ - { - "Ref": "ValidRole01" - } - ], - "PolicyName": "valid_policy_02", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:StartInstances" - ], - "Resource": [ - "*" - ] - }, - { - "Effect": "Allow", - "Action": "*", - "Resource": { - "Fn::GetAtt": [ - "ValidRole01", - "Arn" - ] - } - } - ] - } - } - }, - "ValidPolicy03": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Roles": [ - { - "Ref": "ValidRole01" - } - ], - "PolicyName": "valid_policy_03", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Deny", - "Action": "*", - "Resource": "*" - } - } - } - }, - "InvalidRole01": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ - "ec2.amazonaws.com" - ] - }, - "Action": [ - "sts:AssumeRole" - ] - } - ] - }, - "Policies": [ - { - "PolicyName": "invalid_role_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } - ] - } - } - ] - } - }, - "InvalidUser01": { - "Type": "AWS::IAM::User", - "Properties": { - "Policies": [ - { - "PolicyName": "invalid_user_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } - ] - } - } - ] - } - }, - "InvalidGroup01": { - "Type": "AWS::IAM::Group", - "Properties": { - "Policies": [ - { - "PolicyName": "invalid_group_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } - ] - } - } - ] - } - }, - "InvalidPolicy01": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Roles": [ - { - "Ref": "InvalidRole01" - } - ], - "PolicyName": "invalid_policy_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } - ] - } - } - }, - "InvalidPolicy02": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Roles": [ - { - "Ref": "InvalidRole01" - } - ], - "PolicyName": "invalid_policy_02", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "*", - "elasticache:DescribeCacheClusters" - ], - "Resource": [ - "*" - ] - } - ] - } - } - }, - "InvalidPolicy03": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Roles": [ - { - "Ref": "InvalidRole01" - } - ], - "PolicyName": "invalid_policy_03", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": [ - "*", - "elasticache:DescribeCacheClusters" - ], - "Resource": [ - "*" - ] - } - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("admin_policy_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/iam/inputs/policy_infra.rego b/rego/tests/rules/cfn/iam/inputs/policy_infra.rego index afa5d6a0..ce6d6f39 100644 --- a/rego/tests/rules/cfn/iam/inputs/policy_infra.rego +++ b/rego/tests/rules/cfn/iam/inputs/policy_infra.rego @@ -16,95 +16,17 @@ # # tests/rules/cfn/iam/inputs/policy_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.iam.inputs.policy_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "Group01": { - "Type": "AWS::IAM::Group" - }, - "ValidPolicy01": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Groups": [ - { - "Ref": "Group01" - } - ], - "PolicyName": "valid_policy_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Action": [ - "ec2:StartInstances" - ], - "Resource": [ - "*" - ] - } - } - } - }, - "User01": { - "Type": "AWS::IAM::User" - }, - "InvalidPolicy01": { - "Type": "AWS::IAM::Policy", - "Properties": { - "Users": [ - { - "Ref": "InvalidUser01" - } - ], - "PolicyName": "invalid_policy_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ec2:StartInstances" - ], - "Resource": [ - "*" - ] - } - ] - } - } - }, - "InvalidUser01": { - "Type": "AWS::IAM::User", - "Properties": { - "Policies": [ - { - "PolicyName": "invalid_user_01", - "PolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": "*", - "Resource": "*" - } - ] - } - } - ] - } - } - } -} + +import data.regula + +mock_config := regula_load_type("policy_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego b/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego index 4b6e33a0..10c0387a 100644 --- a/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego +++ b/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego @@ -16,83 +16,17 @@ # # tests/rules/cfn/kms/inputs/key_rotation_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.kms.inputs.key_rotation_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Parameters": { - "KeyManager": { - "Type": "String", - "Default": "user/jasper" - } - }, - "Resources": { - "ValidKey01": { - "Type": "AWS::KMS::Key", - "Properties": { - "EnableKeyRotation": true, - "KeyPolicy": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:${KeyManager}" - } - }, - "Action": "kms:*", - "Resource": "*" - } - } - } - }, - "InvalidKey01": { - "Type": "AWS::KMS::Key", - "Properties": { - "EnableKeyRotation": false, - "KeyPolicy": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:${KeyManager}" - } - }, - "Action": "kms:*", - "Resource": "*" - } - } - } - }, - "InvalidKey02": { - "Type": "AWS::KMS::Key", - "Properties": { - "KeyPolicy": { - "Version": "2012-10-17", - "Statement": { - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:${KeyManager}" - } - }, - "Action": "kms:*", - "Resource": "*" - } - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("key_rotation_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego index 4eeee762..a7e85017 100644 --- a/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego @@ -16,21 +16,17 @@ # # tests/rules/cfn/lambda/inputs/empty_template_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.empty_template_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Empty template for testing", - "Resources": {} -} + +import data.regula + +mock_config := regula_load_type("empty_template_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego index 807d5769..27fb7275 100644 --- a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego @@ -16,231 +16,17 @@ # # tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.invalid_function_not_public_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid public function configuration", - "Resources": { - "FunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Path": "/" - } - }, - "Function": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermissionByArn": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - }, - "Function2": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermissionByRef": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Ref": "Function2" - }, - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - }, - "Function3": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermissionByPartialArn": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::Sub": "${AWS::AccountId}:${Function3}" - }, - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - }, - "Function4": { - "Type": "AWS::Lambda::Function", - "Properties": { - "FunctionName": "function4", - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermissionByHardcodedName": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": "function4", - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - }, - "Function5Alias": { - "Type": "AWS::Lambda::Alias", - "Properties": { - "FunctionName": { - "Ref": "Function5" - }, - "FunctionVersion": "$LATEST", - "Name": "v1" - } - }, - "Function5": { - "Type": "AWS::Lambda::Function", - "Properties": { - "FunctionName": "function5", - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermissionByHardcodedNameAndAlias": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": "function5:v1", - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - }, - "Function6Alias": { - "Type": "AWS::Lambda::Alias", - "Properties": { - "FunctionName": { - "Ref": "Function5" - }, - "FunctionVersion": "$LATEST", - "Name": "v1" - } - }, - "Function6": { - "Type": "AWS::Lambda::Function", - "Properties": { - "FunctionName": { - "Fn::Sub": "function-${AWS::Region}" - }, - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermissionByNameAndAliasUsingFunctions": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::Join": [ - ":", - [ - { - "Fn::Sub": "function-${AWS::Region}" - }, - "v2" - ] - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_function_not_public_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego index c81cebd8..bd69b534 100644 --- a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego @@ -16,44 +16,17 @@ # # tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.invalid_function_not_public_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Invalid public function configuration", - "Resources": { - "Function": { - "Type": "AWS::Serverless::Function", - "Properties": { - "InlineCode": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n", - "Handler": "index.handler", - "Runtime": "nodejs12.x" - } - }, - "FunctionPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_function_not_public_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego index c051bd03..b23b88e4 100644 --- a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego @@ -16,85 +16,17 @@ # # tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.invalid_function_not_public_with_valid_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid public function with both valid and invalid permissions", - "Resources": { - "FunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Path": "/" - } - }, - "Function": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "ValidFunctionPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com" - } - }, - "InvalidFunctionPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": "*" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_function_not_public_with_valid_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego index 503611f5..c8d97f3d 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego @@ -16,74 +16,17 @@ # # tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_account_permission_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid private function configuration with account permission", - "Resources": { - "FunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Path": "/" - } - }, - "Function": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": { - "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root" - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_function_not_public_account_permission_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego index 96d5c224..cd108292 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego @@ -16,46 +16,17 @@ # # tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_account_permission_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Valid private function configuration with account permission", - "Resources": { - "Function": { - "Type": "AWS::Serverless::Function", - "Properties": { - "InlineCode": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n", - "Handler": "index.handler", - "Runtime": "nodejs12.x" - } - }, - "FunctionPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": { - "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root" - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_function_not_public_account_permission_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego index 00d1f292..771fe8b9 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego @@ -16,59 +16,17 @@ # # tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid private function configuration", - "Resources": { - "FunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Path": "/" - } - }, - "Function": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_function_not_public_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego index 23e6c4af..a07c29ec 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego @@ -16,31 +16,17 @@ # # tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Valid private function configuration", - "Resources": { - "Function": { - "Type": "AWS::Serverless::Function", - "Properties": { - "InlineCode": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n", - "Handler": "index.handler", - "Runtime": "nodejs12.x" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_function_not_public_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego index 55383a79..86eddb49 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego @@ -16,72 +16,17 @@ # # tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_service_permission_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid private function configuration with service permission", - "Resources": { - "FunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": "lambda.amazonaws.com" - }, - "Action": "sts:AssumeRole" - } - ] - }, - "ManagedPolicyArns": [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" - ], - "Path": "/" - } - }, - "Function": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" - }, - "Handler": "index.handler", - "Role": { - "Fn::GetAtt": [ - "FunctionRole", - "Arn" - ] - }, - "Runtime": "nodejs12.x" - } - }, - "FunctionPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_function_not_public_service_permission_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego index 5cc0dd61..939a0255 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego @@ -16,44 +16,17 @@ # # tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_service_permission_sam_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Transform": "AWS::Serverless-2016-10-31", - "Description": "Valid private function configuration with service permission", - "Resources": { - "Function": { - "Type": "AWS::Serverless::Function", - "Properties": { - "InlineCode": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n", - "Handler": "index.handler", - "Runtime": "nodejs12.x" - } - }, - "FunctionPermission": { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { - "Fn::GetAtt": [ - "Function", - "Arn" - ] - }, - "Action": "lambda:InvokeFunction", - "Principal": "apigateway.amazonaws.com" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_function_not_public_service_permission_sam_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego index 45db5563..97f1cf59 100644 --- a/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego @@ -16,21 +16,17 @@ # # tests/rules/cfn/s3/inputs/empty_template_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.empty_template_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Empty template for testing", - "Resources": {} -} + +import data.regula + +mock_config := regula_load_type("empty_template_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego index df84d3d6..4a279f6f 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego @@ -16,39 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_block_public_access_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid S3 block public access configuration", - "Resources": { - "Bucket1": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "Private" - } - }, - "Bucket2": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "Private", - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_block_public_access_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego index 72b91bb0..df624d32 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego @@ -16,99 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_all_one_bucket_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "All", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/" - } - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego index 28153bce..1c29d389 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego @@ -16,31 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_no_trails_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail S3 data logging with no CloudTrail", - "Resources": { - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego index d1e5516e..4d0129c2 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego @@ -16,99 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_read_one_bucket_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "ReadOnly", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/" - } - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego index af761b34..cbe86ca6 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego @@ -16,95 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail S3 data logging configuration with no data events", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "All" - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego index c5ac6c8d..c4fc7289 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego @@ -16,90 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_trail_no_selector_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail S3 data logging configuration with no selector", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail" - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego index 25327bea..b4b9b237 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego @@ -16,99 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_write_one_bucket_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "WriteOnly", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/" - } - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego index a37332bb..06cf527a 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego @@ -16,116 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "ReadOnly", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - "arn:aws:s3:::" - ] - } - ] - }, - { - "ReadWriteType": "All", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/" - } - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego index a704780b..117ca3b5 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego @@ -16,25 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_encryption_missing_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid S3 encryption configuration", - "Resources": { - "Bucket": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_encryption_missing_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego index 324d1c5c..9b19b105 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego @@ -16,66 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_encryption_with_valid_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid and valid S3 encryption configurations", - "Resources": { - "KMSKey": { - "Type": "AWS::KMS::Key", - "Properties": { - "Description": "This key is used to encrypt bucket objects", - "KeyPolicy": { - "Version": "2012-10-17", - "Id": "default-key-policy", - "Statement": [ - { - "Sid": "Enable IAM User Permissions", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root" - } - }, - "Action": "kms:*", - "Resource": "*" - } - ] - }, - "PendingWindowInDays": 10 - } - }, - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Ref": "KMSKey" - }, - "SSEAlgorithm": "aws:kms" - } - } - ] - } - } - }, - "InvalidBucket": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_encryption_with_valid_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego index e8aa91bc..13542a6e 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego @@ -16,93 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_https_access_bucket_policy_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid S3 HTTPS access configuration", - "Resources": { - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket1Policy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "Bucket1" - }, - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "s3:Get*", - "Resource": [ - { - "Fn::GetAtt": [ - "Bucket1", - "Arn" - ] - } - ] - }, - { - "Effect": "Allow", - "Principal": "*", - "Action": "*", - "Resource": [ - { - "Fn::GetAtt": [ - "Bucket1", - "Arn" - ] - } - ], - "Condition": { - "Bool": { - "aws:SecureTransport": false - } - } - } - ] - } - } - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2Policy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "Bucket2" - }, - "PolicyDocument": { - "Statement": [ - { - "Effect": "Deny", - "Principal": "*", - "Action": "*", - "Resource": [ - { - "Fn::Sub": "${Bucket2.Arn}/*" - } - ] - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_https_access_bucket_policy_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego index d3fd0d36..75558a12 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego @@ -16,50 +16,17 @@ # # tests/rules/cfn/s3/inputs/invalid_missing_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_missing_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Invalid S3 HTTPS access configuration", - "Resources": { - "Bucket": { - "Type": "AWS::S3::Bucket" - }, - "BucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "Bucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "s3:Get*", - "Resource": [ - { - "Fn::GetAtt": [ - "Bucket", - "Arn" - ] - } - ] - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("invalid_missing_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego index 6ad81f35..fa8ac461 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego @@ -16,34 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_block_public_access_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_block_public_access_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid S3 block public access configuration", - "Resources": { - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "Private", - "PublicAccessBlockConfiguration": { - "BlockPublicAcls": true, - "BlockPublicPolicy": true, - "IgnorePublicAcls": true, - "RestrictPublicBuckets": true - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_block_public_access_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego index c768f773..71896519 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego @@ -16,103 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_all_all_buckets_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "All", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - "arn:aws:s3:::" - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego index 12bf2aa1..d1493fc2 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego @@ -16,102 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_all_two_buckets_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "All", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/" - }, - { - "Fn::Sub": "${Bucket1.Arn}/" - } - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego index 56500dc2..21333bff 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego @@ -16,103 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_read_all_buckets_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "ReadOnly", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - "arn:aws:s3:::" - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego index 9c106a5c..c6015f12 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego @@ -16,103 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_write_all_buckets_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "WriteOnly", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - "arn:aws:s3:::" - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego index 63bb7e3e..a6a81751 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego @@ -16,116 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid CloudTrail S3 data logging configuration", - "Resources": { - "CloudTrailLogging": { - "Type": "AWS::CloudTrail::Trail", - "Properties": { - "IsLogging": true, - "S3BucketName": { - "Ref": "LoggingBucket" - }, - "TrailName": "cf-fuguetest-trail", - "EventSelectors": [ - { - "ReadWriteType": "ReadOnly", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - "arn:aws:s3:::" - ] - } - ] - }, - { - "ReadWriteType": "All", - "DataResources": [ - { - "Type": "AWS::S3::Object", - "Values": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/" - } - ] - } - ] - } - ] - } - }, - "LoggingBucket": { - "Type": "AWS::S3::Bucket" - }, - "LoggingBucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "LoggingBucket" - }, - "PolicyDocument": { - "Statement": [ - { - "Sid": "AWSCloudTrailAclCheck", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:GetBucketAcl", - "Resource": [ - { - "Fn::GetAtt": [ - "LoggingBucket", - "Arn" - ] - } - ] - }, - { - "Sid": "AWSCloudTrailWrite", - "Effect": "Allow", - "Principal": { - "Service": "cloudtrail.amazonaws.com" - }, - "Action": "s3:PutObject", - "Resource": [ - { - "Fn::Sub": "${LoggingBucket.Arn}/*" - } - ], - "Condition": { - "StringEquals": { - "s3:x-amz-acl": "bucket-owner-full-control" - } - } - } - ] - } - } - }, - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket3": { - "Type": "AWS::S3::Bucket" - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego index 59dff344..431d0aa0 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego @@ -16,63 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_encryption_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_encryption_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid S3 encryption configuration", - "Resources": { - "KMSKey": { - "Type": "AWS::KMS::Key", - "Properties": { - "Description": "This key is used to encrypt bucket objects", - "KeyPolicy": { - "Version": "2012-10-17", - "Id": "default-key-policy", - "Statement": [ - { - "Sid": "Enable IAM User Permissions", - "Effect": "Allow", - "Principal": { - "AWS": { - "Fn::Sub": "arn:aws:iam::${AWS::AccountId}:root" - } - }, - "Action": "kms:*", - "Resource": "*" - } - ] - }, - "PendingWindowInDays": 10 - } - }, - "Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "BucketEncryption": { - "ServerSideEncryptionConfiguration": [ - { - "ServerSideEncryptionByDefault": { - "KMSMasterKeyID": { - "Ref": "KMSKey" - }, - "SSEAlgorithm": "aws:kms" - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_encryption_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego index 0341acc2..7f24dab1 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego @@ -16,98 +16,17 @@ # # tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_https_access_bucket_policy_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Valid S3 HTTPS access configuration", - "Resources": { - "Bucket1": { - "Type": "AWS::S3::Bucket" - }, - "Bucket1Policy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "Bucket1" - }, - "PolicyDocument": { - "Statement": [ - { - "Effect": "Allow", - "Principal": "*", - "Action": "s3:Get*", - "Resource": [ - { - "Fn::GetAtt": [ - "Bucket1", - "Arn" - ] - } - ] - }, - { - "Effect": "Deny", - "Principal": "*", - "Action": "*", - "Resource": [ - { - "Fn::GetAtt": [ - "Bucket1", - "Arn" - ] - } - ], - "Condition": { - "Bool": { - "aws:SecureTransport": false - } - } - } - ] - } - } - }, - "Bucket2": { - "Type": "AWS::S3::Bucket" - }, - "Bucket2Policy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "Bucket": { - "Ref": "Bucket2" - }, - "PolicyDocument": { - "Statement": [ - { - "Effect": "Deny", - "Principal": "*", - "Action": "*", - "Resource": [ - { - "Fn::Sub": "${Bucket2.Arn}/*" - } - ], - "Condition": { - "Bool": { - "aws:SecureTransport": false - } - } - } - ] - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("valid_https_access_bucket_policy_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego b/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego index 1a7e85ab..cc26b60d 100644 --- a/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego @@ -16,62 +16,17 @@ # # tests/rules/cfn/vpc/inputs/default_security_group_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.default_security_group_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "Resources": { - "Vpc01": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "Vpc01InvalidIngress": { - "Type": "AWS::EC2::SecurityGroupIngress", - "Properties": { - "GroupId": { - "Fn::GetAtt": [ - "Vpc01", - "DefaultSecurityGroup" - ] - }, - "IpProtocol": "tcp", - "FromPort": 22, - "ToPort": 22, - "CidrIp": "0.0.0.0/0" - } - }, - "Vpc01SecurityGroup": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description" - } - }, - "Vpc01ValidIngress": { - "Type": "AWS::EC2::SecurityGroupIngress", - "Properties": { - "GroupId": { - "Ref": "Vpc01SecurityGroup" - }, - "IpProtocol": "tcp", - "FromPort": 22, - "ToPort": 22, - "CidrIp": "0.0.0.0/0" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("default_security_group_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego b/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego index a34cc534..ecaefe62 100644 --- a/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego @@ -16,43 +16,17 @@ # # tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.flow_logging_enabled_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "ValidVpc": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidVpcFlowLog": { - "Type": "AWS::EC2::FlowLog", - "Properties": { - "ResourceId": { - "Ref": "ValidVpc" - }, - "ResourceType": "VPC", - "TrafficType": "REJECT" - } - }, - "InvalidVpc": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - } - } -} + +import data.regula + +mock_config := regula_load_type("flow_logging_enabled_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego b/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego index f445f6cb..27ebc998 100644 --- a/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego @@ -16,161 +16,17 @@ # # tests/rules/cfn/vpc/inputs/ingress_22_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.ingress_22_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "Resources": { - "Vpc01": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidSecurityGroup01": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description" - } - }, - "ValidSecurityGroup02": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 80, - "ToPort": 80, - "IpProtocol": -1 - } - ] - } - }, - "ValidSecurityGroup03": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description", - "SecurityGroupIngress": [ - { - "CidrIpv6": "::/0", - "FromPort": 80, - "ToPort": 80, - "IpProtocol": -1 - } - ] - } - }, - "ValidSecurityGroup04": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description" - } - }, - "ValidSecurityGroup04Ingress01": { - "Type": "AWS::EC2::SecurityGroupIngress", - "Properties": { - "GroupId": { - "Ref": "ValidSecurityGroup04" - }, - "CidrIp": "192.168.1.7/32", - "FromPort": 22, - "ToPort": 22, - "IpProtocol": -1 - } - }, - "InvalidSecurityGroup01": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 22, - "ToPort": 22, - "IpProtocol": -1 - } - ] - } - }, - "InvalidSecurityGroup02": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": -1, - "IpProtocol": -1 - } - ] - } - }, - "InvalidSecurityGroup03": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description", - "SecurityGroupIngress": [ - { - "CidrIpv6": "::/0", - "FromPort": 20, - "ToPort": 24, - "IpProtocol": -1 - } - ] - } - }, - "InvalidSecurityGroup04": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description" - } - }, - "InvalidSecurityGroup04Ingress01": { - "Type": "AWS::EC2::SecurityGroupIngress", - "Properties": { - "GroupId": { - "Ref": "ValidSecurityGroup04" - }, - "CidrIp": "0.0.0.0/0", - "FromPort": 22, - "ToPort": 22, - "IpProtocol": -1 - } - } - } -} + +import data.regula + +mock_config := regula_load_type("ingress_22_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego b/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego index 2bd98d9d..314ea63d 100644 --- a/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego @@ -16,60 +16,17 @@ # # tests/rules/cfn/vpc/inputs/ingress_3389_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.ingress_3389_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "Resources": { - "Vpc01": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidSecurityGroup02": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 80, - "ToPort": 80, - "IpProtocol": -1 - } - ] - } - }, - "InvalidSecurityGroup01": { - "Type": "AWS::EC2::SecurityGroup", - "Properties": { - "VpcId": { - "Ref": "Vpc01" - }, - "GroupDescription": "Description", - "SecurityGroupIngress": [ - { - "CidrIp": "0.0.0.0/0", - "FromPort": 3389, - "ToPort": 3389, - "IpProtocol": -1 - } - ] - } - } - } -} + +import data.regula + +mock_config := regula_load_type("ingress_3389_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego index c6d940f3..0339cd42 100644 --- a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego @@ -16,222 +16,17 @@ # # tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.nacl_ingress_22_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "ValidVpc01": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidVpc01Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "ValidVpc01" - } - } - }, - "ValidVpc01NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc01Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": 6, - "CidrBlock": "0.0.0.0/0" - } - }, - "ValidVpc02": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidVpc02Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "ValidVpc02" - } - } - }, - "ValidVpc02NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc02Nacl" - }, - "RuleNumber": 10, - "RuleAction": "deny", - "Protocol": 6, - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": 22, - "To": 22 - } - } - }, - "ValidVpc02NaclEntry02": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc02Nacl" - }, - "RuleNumber": 20, - "RuleAction": "allow", - "Protocol": 6, - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": 0, - "To": 1000 - } - } - }, - "ValidVpc03": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidVpc03Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "ValidVpc03" - } - } - }, - "ValidVpc03NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc03Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": -1, - "CidrBlock": "0.0.0.0/0", - "Egress": true - } - }, - "InvalidVpc01": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "InvalidVpc01Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "InvalidVpc01" - } - } - }, - "InvalidVpc01NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc01Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": -1, - "CidrBlock": "0.0.0.0/0" - } - }, - "InvalidVpc02": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "InvalidVpc02Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "InvalidVpc02" - } - } - }, - "InvalidVpc02NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc02Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": 1, - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": 22, - "To": 22 - } - } - }, - "InvalidVpc03": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "InvalidVpc03Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "InvalidVpc03" - } - } - }, - "InvalidVpc03NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc03Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": 6, - "Ipv6CidrBlock": "::/0", - "PortRange": { - "From": 22, - "To": 22 - } - } - }, - "InvalidVpc03NaclEntry02": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc03Nacl" - }, - "RuleNumber": 20, - "RuleAction": "deny", - "Protocol": 6, - "Ipv6CidrBlock": "::/0", - "PortRange": { - "From": 0, - "To": 1000 - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("nacl_ingress_22_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego index 5e5c9027..dd1dc756 100644 --- a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego @@ -16,222 +16,17 @@ # # tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.cfn # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.nacl_ingress_3389_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "ValidVpc01": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidVpc01Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "ValidVpc01" - } - } - }, - "ValidVpc01NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc01Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": 6, - "CidrBlock": "0.0.0.0/0" - } - }, - "ValidVpc02": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidVpc02Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "ValidVpc02" - } - } - }, - "ValidVpc02NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc02Nacl" - }, - "RuleNumber": 10, - "RuleAction": "deny", - "Protocol": 6, - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": 3389, - "To": 3389 - } - } - }, - "ValidVpc02NaclEntry02": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc02Nacl" - }, - "RuleNumber": 20, - "RuleAction": "allow", - "Protocol": 6, - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": 0, - "To": 10000 - } - } - }, - "ValidVpc03": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "ValidVpc03Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "ValidVpc03" - } - } - }, - "ValidVpc03NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "ValidVpc03Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": -1, - "CidrBlock": "0.0.0.0/0", - "Egress": true - } - }, - "InvalidVpc01": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "InvalidVpc01Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "InvalidVpc01" - } - } - }, - "InvalidVpc01NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc01Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": -1, - "CidrBlock": "0.0.0.0/0" - } - }, - "InvalidVpc02": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "InvalidVpc02Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "InvalidVpc02" - } - } - }, - "InvalidVpc02NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc02Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": 1, - "CidrBlock": "0.0.0.0/0", - "PortRange": { - "From": 3389, - "To": 3389 - } - } - }, - "InvalidVpc03": { - "Type": "AWS::EC2::VPC", - "Properties": { - "CidrBlock": "10.0.0.0/16" - } - }, - "InvalidVpc03Nacl": { - "Type": "AWS::EC2::NetworkAcl", - "Properties": { - "VpcId": { - "Ref": "InvalidVpc03" - } - } - }, - "InvalidVpc03NaclEntry01": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc03Nacl" - }, - "RuleNumber": 10, - "RuleAction": "allow", - "Protocol": 6, - "Ipv6CidrBlock": "::/0", - "PortRange": { - "From": 3389, - "To": 3389 - } - } - }, - "InvalidVpc03NaclEntry02": { - "Type": "AWS::EC2::NetworkAclEntry", - "Properties": { - "NetworkAclId": { - "Ref": "InvalidVpc03Nacl" - }, - "RuleNumber": 20, - "RuleAction": "deny", - "Protocol": 6, - "Ipv6CidrBlock": "::/0", - "PortRange": { - "From": 0, - "To": 10000 - } - } - } - } -} + +import data.regula + +mock_config := regula_load_type("nacl_ingress_3389_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego index 145fc757..20bcdc42 100644 --- a/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego +++ b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,1172 +16,17 @@ # # tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.cloudfront.inputs.distribution_https_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_cloudfront_distribution.allow_all", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "allow_all", - "provider_name": "aws", - "schema_version": 1, - "values": { - "aliases": null, - "comment": null, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - "GET", - "HEAD" - ], - "cached_methods": [ - "GET", - "HEAD" - ], - "compress": false, - "default_ttl": 86400, - "field_level_encryption_id": null, - "forwarded_values": [ - { - "cookies": [ - { - "forward": "none", - "whitelisted_names": null - } - ], - "headers": null, - "query_string": false, - "query_string_cache_keys": null - } - ], - "lambda_function_association": [], - "max_ttl": 31536000, - "min_ttl": 0, - "smooth_streaming": null, - "trusted_signers": null, - "viewer_protocol_policy": "allow-all" - } - ], - "default_root_object": null, - "enabled": true, - "http_version": "http2", - "is_ipv6_enabled": false, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "origin_path": "", - "s3_origin_config": [ - {} - ] - } - ], - "origin_group": [], - "price_class": "PriceClass_All", - "restrictions": [ - { - "geo_restriction": [ - { - "locations": null, - "restriction_type": "none" - } - ] - } - ], - "retain_on_delete": false, - "tags": null, - "viewer_certificate": [ - { - "acm_certificate_arn": null, - "cloudfront_default_certificate": true, - "iam_certificate_id": null, - "minimum_protocol_version": "TLSv1", - "ssl_support_method": null - } - ], - "wait_for_deployment": true, - "web_acl_id": null - } - }, - { - "address": "aws_cloudfront_distribution.https_only", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "https_only", - "provider_name": "aws", - "schema_version": 1, - "values": { - "aliases": null, - "comment": null, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - "GET", - "HEAD" - ], - "cached_methods": [ - "GET", - "HEAD" - ], - "compress": false, - "default_ttl": 86400, - "field_level_encryption_id": null, - "forwarded_values": [ - { - "cookies": [ - { - "forward": "none", - "whitelisted_names": null - } - ], - "headers": null, - "query_string": false, - "query_string_cache_keys": null - } - ], - "lambda_function_association": [], - "max_ttl": 31536000, - "min_ttl": 0, - "smooth_streaming": null, - "trusted_signers": null, - "viewer_protocol_policy": "https-only" - } - ], - "default_root_object": null, - "enabled": true, - "http_version": "http2", - "is_ipv6_enabled": false, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "origin_path": "", - "s3_origin_config": [ - {} - ] - } - ], - "origin_group": [], - "price_class": "PriceClass_All", - "restrictions": [ - { - "geo_restriction": [ - { - "locations": null, - "restriction_type": "none" - } - ] - } - ], - "retain_on_delete": false, - "tags": null, - "viewer_certificate": [ - { - "acm_certificate_arn": null, - "cloudfront_default_certificate": true, - "iam_certificate_id": null, - "minimum_protocol_version": "TLSv1", - "ssl_support_method": null - } - ], - "wait_for_deployment": true, - "web_acl_id": null - } - }, - { - "address": "aws_cloudfront_distribution.redirect_to_https", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "redirect_to_https", - "provider_name": "aws", - "schema_version": 1, - "values": { - "aliases": null, - "comment": null, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - "GET", - "HEAD" - ], - "cached_methods": [ - "GET", - "HEAD" - ], - "compress": false, - "default_ttl": 86400, - "field_level_encryption_id": null, - "forwarded_values": [ - { - "cookies": [ - { - "forward": "none", - "whitelisted_names": null - } - ], - "headers": null, - "query_string": false, - "query_string_cache_keys": null - } - ], - "lambda_function_association": [], - "max_ttl": 31536000, - "min_ttl": 0, - "smooth_streaming": null, - "trusted_signers": null, - "viewer_protocol_policy": "redirect-to-https" - } - ], - "default_root_object": null, - "enabled": true, - "http_version": "http2", - "is_ipv6_enabled": false, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "origin_path": "", - "s3_origin_config": [ - {} - ] - } - ], - "origin_group": [], - "price_class": "PriceClass_All", - "restrictions": [ - { - "geo_restriction": [ - { - "locations": null, - "restriction_type": "none" - } - ] - } - ], - "retain_on_delete": false, - "tags": null, - "viewer_certificate": [ - { - "acm_certificate_arn": null, - "cloudfront_default_certificate": true, - "iam_certificate_id": null, - "minimum_protocol_version": "TLSv1", - "ssl_support_method": null - } - ], - "wait_for_deployment": true, - "web_acl_id": null - } - }, - { - "address": "aws_cloudfront_origin_access_identity.origin_access_identity", - "mode": "managed", - "type": "aws_cloudfront_origin_access_identity", - "name": "origin_access_identity", - "provider_name": "aws", - "schema_version": 0, - "values": { - "comment": null - } - }, - { - "address": "aws_s3_bucket.bucket", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "bucket", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_cloudfront_distribution.allow_all", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "allow_all", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "aliases": null, - "comment": null, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - "GET", - "HEAD" - ], - "cached_methods": [ - "GET", - "HEAD" - ], - "compress": false, - "default_ttl": 86400, - "field_level_encryption_id": null, - "forwarded_values": [ - { - "cookies": [ - { - "forward": "none", - "whitelisted_names": null - } - ], - "headers": null, - "query_string": false, - "query_string_cache_keys": null - } - ], - "lambda_function_association": [], - "max_ttl": 31536000, - "min_ttl": 0, - "smooth_streaming": null, - "trusted_signers": null, - "viewer_protocol_policy": "allow-all" - } - ], - "default_root_object": null, - "enabled": true, - "http_version": "http2", - "is_ipv6_enabled": false, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "origin_path": "", - "s3_origin_config": [ - {} - ] - } - ], - "origin_group": [], - "price_class": "PriceClass_All", - "restrictions": [ - { - "geo_restriction": [ - { - "locations": null, - "restriction_type": "none" - } - ] - } - ], - "retain_on_delete": false, - "tags": null, - "viewer_certificate": [ - { - "acm_certificate_arn": null, - "cloudfront_default_certificate": true, - "iam_certificate_id": null, - "minimum_protocol_version": "TLSv1", - "ssl_support_method": null - } - ], - "wait_for_deployment": true, - "web_acl_id": null - }, - "after_unknown": { - "arn": true, - "caller_reference": true, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - false, - false - ], - "cached_methods": [ - false, - false - ], - "forwarded_values": [ - { - "cookies": [ - {} - ] - } - ], - "lambda_function_association": [], - "target_origin_id": true - } - ], - "domain_name": true, - "etag": true, - "hosted_zone_id": true, - "id": true, - "in_progress_validation_batches": true, - "last_modified_time": true, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "domain_name": true, - "origin_id": true, - "s3_origin_config": [ - { - "origin_access_identity": true - } - ] - } - ], - "origin_group": [], - "restrictions": [ - { - "geo_restriction": [ - {} - ] - } - ], - "status": true, - "trusted_signers": true, - "viewer_certificate": [ - {} - ] - } - } - }, - { - "address": "aws_cloudfront_distribution.https_only", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "https_only", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "aliases": null, - "comment": null, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - "GET", - "HEAD" - ], - "cached_methods": [ - "GET", - "HEAD" - ], - "compress": false, - "default_ttl": 86400, - "field_level_encryption_id": null, - "forwarded_values": [ - { - "cookies": [ - { - "forward": "none", - "whitelisted_names": null - } - ], - "headers": null, - "query_string": false, - "query_string_cache_keys": null - } - ], - "lambda_function_association": [], - "max_ttl": 31536000, - "min_ttl": 0, - "smooth_streaming": null, - "trusted_signers": null, - "viewer_protocol_policy": "https-only" - } - ], - "default_root_object": null, - "enabled": true, - "http_version": "http2", - "is_ipv6_enabled": false, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "origin_path": "", - "s3_origin_config": [ - {} - ] - } - ], - "origin_group": [], - "price_class": "PriceClass_All", - "restrictions": [ - { - "geo_restriction": [ - { - "locations": null, - "restriction_type": "none" - } - ] - } - ], - "retain_on_delete": false, - "tags": null, - "viewer_certificate": [ - { - "acm_certificate_arn": null, - "cloudfront_default_certificate": true, - "iam_certificate_id": null, - "minimum_protocol_version": "TLSv1", - "ssl_support_method": null - } - ], - "wait_for_deployment": true, - "web_acl_id": null - }, - "after_unknown": { - "arn": true, - "caller_reference": true, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - false, - false - ], - "cached_methods": [ - false, - false - ], - "forwarded_values": [ - { - "cookies": [ - {} - ] - } - ], - "lambda_function_association": [], - "target_origin_id": true - } - ], - "domain_name": true, - "etag": true, - "hosted_zone_id": true, - "id": true, - "in_progress_validation_batches": true, - "last_modified_time": true, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "domain_name": true, - "origin_id": true, - "s3_origin_config": [ - { - "origin_access_identity": true - } - ] - } - ], - "origin_group": [], - "restrictions": [ - { - "geo_restriction": [ - {} - ] - } - ], - "status": true, - "trusted_signers": true, - "viewer_certificate": [ - {} - ] - } - } - }, - { - "address": "aws_cloudfront_distribution.redirect_to_https", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "redirect_to_https", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "aliases": null, - "comment": null, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - "GET", - "HEAD" - ], - "cached_methods": [ - "GET", - "HEAD" - ], - "compress": false, - "default_ttl": 86400, - "field_level_encryption_id": null, - "forwarded_values": [ - { - "cookies": [ - { - "forward": "none", - "whitelisted_names": null - } - ], - "headers": null, - "query_string": false, - "query_string_cache_keys": null - } - ], - "lambda_function_association": [], - "max_ttl": 31536000, - "min_ttl": 0, - "smooth_streaming": null, - "trusted_signers": null, - "viewer_protocol_policy": "redirect-to-https" - } - ], - "default_root_object": null, - "enabled": true, - "http_version": "http2", - "is_ipv6_enabled": false, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "origin_path": "", - "s3_origin_config": [ - {} - ] - } - ], - "origin_group": [], - "price_class": "PriceClass_All", - "restrictions": [ - { - "geo_restriction": [ - { - "locations": null, - "restriction_type": "none" - } - ] - } - ], - "retain_on_delete": false, - "tags": null, - "viewer_certificate": [ - { - "acm_certificate_arn": null, - "cloudfront_default_certificate": true, - "iam_certificate_id": null, - "minimum_protocol_version": "TLSv1", - "ssl_support_method": null - } - ], - "wait_for_deployment": true, - "web_acl_id": null - }, - "after_unknown": { - "arn": true, - "caller_reference": true, - "custom_error_response": [], - "default_cache_behavior": [ - { - "allowed_methods": [ - false, - false - ], - "cached_methods": [ - false, - false - ], - "forwarded_values": [ - { - "cookies": [ - {} - ] - } - ], - "lambda_function_association": [], - "target_origin_id": true - } - ], - "domain_name": true, - "etag": true, - "hosted_zone_id": true, - "id": true, - "in_progress_validation_batches": true, - "last_modified_time": true, - "logging_config": [], - "ordered_cache_behavior": [], - "origin": [ - { - "custom_header": [], - "custom_origin_config": [], - "domain_name": true, - "origin_id": true, - "s3_origin_config": [ - { - "origin_access_identity": true - } - ] - } - ], - "origin_group": [], - "restrictions": [ - { - "geo_restriction": [ - {} - ] - } - ], - "status": true, - "trusted_signers": true, - "viewer_certificate": [ - {} - ] - } - } - }, - { - "address": "aws_cloudfront_origin_access_identity.origin_access_identity", - "mode": "managed", - "type": "aws_cloudfront_origin_access_identity", - "name": "origin_access_identity", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "comment": null - }, - "after_unknown": { - "caller_reference": true, - "cloudfront_access_identity_path": true, - "etag": true, - "iam_arn": true, - "id": true, - "s3_canonical_user_id": true - } - } - }, - { - "address": "aws_s3_bucket.bucket", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "bucket", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-1" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_cloudfront_distribution.allow_all", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "allow_all", - "provider_config_key": "aws", - "expressions": { - "default_cache_behavior": [ - { - "allowed_methods": { - "constant_value": [ - "HEAD", - "GET" - ] - }, - "cached_methods": { - "constant_value": [ - "HEAD", - "GET" - ] - }, - "forwarded_values": [ - { - "cookies": [ - { - "forward": { - "constant_value": "none" - } - } - ], - "query_string": { - "constant_value": false - } - } - ], - "target_origin_id": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "viewer_protocol_policy": { - "constant_value": "allow-all" - } - } - ], - "enabled": { - "constant_value": true - }, - "origin": [ - { - "domain_name": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "origin_id": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "s3_origin_config": [ - { - "origin_access_identity": { - "references": [ - "aws_cloudfront_origin_access_identity.origin_access_identity" - ] - } - } - ] - } - ], - "restrictions": [ - { - "geo_restriction": [ - { - "restriction_type": { - "constant_value": "none" - } - } - ] - } - ], - "viewer_certificate": [ - { - "cloudfront_default_certificate": { - "constant_value": true - } - } - ] - }, - "schema_version": 1 - }, - { - "address": "aws_cloudfront_distribution.https_only", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "https_only", - "provider_config_key": "aws", - "expressions": { - "default_cache_behavior": [ - { - "allowed_methods": { - "constant_value": [ - "HEAD", - "GET" - ] - }, - "cached_methods": { - "constant_value": [ - "HEAD", - "GET" - ] - }, - "forwarded_values": [ - { - "cookies": [ - { - "forward": { - "constant_value": "none" - } - } - ], - "query_string": { - "constant_value": false - } - } - ], - "target_origin_id": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "viewer_protocol_policy": { - "constant_value": "https-only" - } - } - ], - "enabled": { - "constant_value": true - }, - "origin": [ - { - "domain_name": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "origin_id": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "s3_origin_config": [ - { - "origin_access_identity": { - "references": [ - "aws_cloudfront_origin_access_identity.origin_access_identity" - ] - } - } - ] - } - ], - "restrictions": [ - { - "geo_restriction": [ - { - "restriction_type": { - "constant_value": "none" - } - } - ] - } - ], - "viewer_certificate": [ - { - "cloudfront_default_certificate": { - "constant_value": true - } - } - ] - }, - "schema_version": 1 - }, - { - "address": "aws_cloudfront_distribution.redirect_to_https", - "mode": "managed", - "type": "aws_cloudfront_distribution", - "name": "redirect_to_https", - "provider_config_key": "aws", - "expressions": { - "default_cache_behavior": [ - { - "allowed_methods": { - "constant_value": [ - "HEAD", - "GET" - ] - }, - "cached_methods": { - "constant_value": [ - "HEAD", - "GET" - ] - }, - "forwarded_values": [ - { - "cookies": [ - { - "forward": { - "constant_value": "none" - } - } - ], - "query_string": { - "constant_value": false - } - } - ], - "target_origin_id": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "viewer_protocol_policy": { - "constant_value": "redirect-to-https" - } - } - ], - "enabled": { - "constant_value": true - }, - "origin": [ - { - "domain_name": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "origin_id": { - "references": [ - "aws_s3_bucket.bucket" - ] - }, - "s3_origin_config": [ - { - "origin_access_identity": { - "references": [ - "aws_cloudfront_origin_access_identity.origin_access_identity" - ] - } - } - ] - } - ], - "restrictions": [ - { - "geo_restriction": [ - { - "restriction_type": { - "constant_value": "none" - } - } - ] - } - ], - "viewer_certificate": [ - { - "cloudfront_default_certificate": { - "constant_value": true - } - } - ] - }, - "schema_version": 1 - }, - { - "address": "aws_cloudfront_origin_access_identity.origin_access_identity", - "mode": "managed", - "type": "aws_cloudfront_origin_access_identity", - "name": "origin_access_identity", - "provider_config_key": "aws", - "schema_version": 0 - }, - { - "address": "aws_s3_bucket.bucket", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "bucket", - "provider_config_key": "aws", - "expressions": { - "force_destroy": { - "constant_value": true - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("distribution_https_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tfplan b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tfplan new file mode 100644 index 00000000..591624dd --- /dev/null +++ b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.tfplan @@ -0,0 +1,1169 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_cloudfront_distribution.allow_all", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "allow_all", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "aliases": null, + "comment": null, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + "GET", + "HEAD" + ], + "cache_policy_id": null, + "cached_methods": [ + "GET", + "HEAD" + ], + "compress": false, + "field_level_encryption_id": null, + "forwarded_values": [ + { + "cookies": [ + { + "forward": "none" + } + ], + "query_string": false + } + ], + "lambda_function_association": [], + "min_ttl": 0, + "origin_request_policy_id": null, + "realtime_log_config_arn": null, + "smooth_streaming": null, + "viewer_protocol_policy": "allow-all" + } + ], + "default_root_object": null, + "enabled": true, + "http_version": "http2", + "is_ipv6_enabled": false, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "origin_path": "", + "s3_origin_config": [ + {} + ] + } + ], + "origin_group": [], + "price_class": "PriceClass_All", + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": "none" + } + ] + } + ], + "retain_on_delete": false, + "tags": null, + "viewer_certificate": [ + { + "acm_certificate_arn": null, + "cloudfront_default_certificate": true, + "iam_certificate_id": null, + "minimum_protocol_version": "TLSv1", + "ssl_support_method": null + } + ], + "wait_for_deployment": true, + "web_acl_id": null + } + }, + { + "address": "aws_cloudfront_distribution.https_only", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "https_only", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "aliases": null, + "comment": null, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + "GET", + "HEAD" + ], + "cache_policy_id": null, + "cached_methods": [ + "GET", + "HEAD" + ], + "compress": false, + "field_level_encryption_id": null, + "forwarded_values": [ + { + "cookies": [ + { + "forward": "none" + } + ], + "query_string": false + } + ], + "lambda_function_association": [], + "min_ttl": 0, + "origin_request_policy_id": null, + "realtime_log_config_arn": null, + "smooth_streaming": null, + "viewer_protocol_policy": "https-only" + } + ], + "default_root_object": null, + "enabled": true, + "http_version": "http2", + "is_ipv6_enabled": false, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "origin_path": "", + "s3_origin_config": [ + {} + ] + } + ], + "origin_group": [], + "price_class": "PriceClass_All", + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": "none" + } + ] + } + ], + "retain_on_delete": false, + "tags": null, + "viewer_certificate": [ + { + "acm_certificate_arn": null, + "cloudfront_default_certificate": true, + "iam_certificate_id": null, + "minimum_protocol_version": "TLSv1", + "ssl_support_method": null + } + ], + "wait_for_deployment": true, + "web_acl_id": null + } + }, + { + "address": "aws_cloudfront_distribution.redirect_to_https", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "redirect_to_https", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "aliases": null, + "comment": null, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + "GET", + "HEAD" + ], + "cache_policy_id": null, + "cached_methods": [ + "GET", + "HEAD" + ], + "compress": false, + "field_level_encryption_id": null, + "forwarded_values": [ + { + "cookies": [ + { + "forward": "none" + } + ], + "query_string": false + } + ], + "lambda_function_association": [], + "min_ttl": 0, + "origin_request_policy_id": null, + "realtime_log_config_arn": null, + "smooth_streaming": null, + "viewer_protocol_policy": "redirect-to-https" + } + ], + "default_root_object": null, + "enabled": true, + "http_version": "http2", + "is_ipv6_enabled": false, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "origin_path": "", + "s3_origin_config": [ + {} + ] + } + ], + "origin_group": [], + "price_class": "PriceClass_All", + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": "none" + } + ] + } + ], + "retain_on_delete": false, + "tags": null, + "viewer_certificate": [ + { + "acm_certificate_arn": null, + "cloudfront_default_certificate": true, + "iam_certificate_id": null, + "minimum_protocol_version": "TLSv1", + "ssl_support_method": null + } + ], + "wait_for_deployment": true, + "web_acl_id": null + } + }, + { + "address": "aws_cloudfront_origin_access_identity.origin_access_identity", + "mode": "managed", + "type": "aws_cloudfront_origin_access_identity", + "name": "origin_access_identity", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "comment": null + } + }, + { + "address": "aws_s3_bucket.bucket", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "bucket", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_cloudfront_distribution.allow_all", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "allow_all", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "aliases": null, + "comment": null, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + "GET", + "HEAD" + ], + "cache_policy_id": null, + "cached_methods": [ + "GET", + "HEAD" + ], + "compress": false, + "field_level_encryption_id": null, + "forwarded_values": [ + { + "cookies": [ + { + "forward": "none" + } + ], + "query_string": false + } + ], + "lambda_function_association": [], + "min_ttl": 0, + "origin_request_policy_id": null, + "realtime_log_config_arn": null, + "smooth_streaming": null, + "viewer_protocol_policy": "allow-all" + } + ], + "default_root_object": null, + "enabled": true, + "http_version": "http2", + "is_ipv6_enabled": false, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "origin_path": "", + "s3_origin_config": [ + {} + ] + } + ], + "origin_group": [], + "price_class": "PriceClass_All", + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": "none" + } + ] + } + ], + "retain_on_delete": false, + "tags": null, + "viewer_certificate": [ + { + "acm_certificate_arn": null, + "cloudfront_default_certificate": true, + "iam_certificate_id": null, + "minimum_protocol_version": "TLSv1", + "ssl_support_method": null + } + ], + "wait_for_deployment": true, + "web_acl_id": null + }, + "after_unknown": { + "arn": true, + "caller_reference": true, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + false, + false + ], + "cached_methods": [ + false, + false + ], + "default_ttl": true, + "forwarded_values": [ + { + "cookies": [ + { + "whitelisted_names": true + } + ], + "headers": true, + "query_string_cache_keys": true + } + ], + "lambda_function_association": [], + "max_ttl": true, + "target_origin_id": true, + "trusted_key_groups": true, + "trusted_signers": true + } + ], + "domain_name": true, + "etag": true, + "hosted_zone_id": true, + "id": true, + "in_progress_validation_batches": true, + "last_modified_time": true, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "domain_name": true, + "origin_id": true, + "s3_origin_config": [ + { + "origin_access_identity": true + } + ] + } + ], + "origin_group": [], + "restrictions": [ + { + "geo_restriction": [ + { + "locations": true + } + ] + } + ], + "status": true, + "tags_all": true, + "trusted_key_groups": true, + "trusted_signers": true, + "viewer_certificate": [ + {} + ] + } + } + }, + { + "address": "aws_cloudfront_distribution.https_only", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "https_only", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "aliases": null, + "comment": null, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + "GET", + "HEAD" + ], + "cache_policy_id": null, + "cached_methods": [ + "GET", + "HEAD" + ], + "compress": false, + "field_level_encryption_id": null, + "forwarded_values": [ + { + "cookies": [ + { + "forward": "none" + } + ], + "query_string": false + } + ], + "lambda_function_association": [], + "min_ttl": 0, + "origin_request_policy_id": null, + "realtime_log_config_arn": null, + "smooth_streaming": null, + "viewer_protocol_policy": "https-only" + } + ], + "default_root_object": null, + "enabled": true, + "http_version": "http2", + "is_ipv6_enabled": false, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "origin_path": "", + "s3_origin_config": [ + {} + ] + } + ], + "origin_group": [], + "price_class": "PriceClass_All", + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": "none" + } + ] + } + ], + "retain_on_delete": false, + "tags": null, + "viewer_certificate": [ + { + "acm_certificate_arn": null, + "cloudfront_default_certificate": true, + "iam_certificate_id": null, + "minimum_protocol_version": "TLSv1", + "ssl_support_method": null + } + ], + "wait_for_deployment": true, + "web_acl_id": null + }, + "after_unknown": { + "arn": true, + "caller_reference": true, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + false, + false + ], + "cached_methods": [ + false, + false + ], + "default_ttl": true, + "forwarded_values": [ + { + "cookies": [ + { + "whitelisted_names": true + } + ], + "headers": true, + "query_string_cache_keys": true + } + ], + "lambda_function_association": [], + "max_ttl": true, + "target_origin_id": true, + "trusted_key_groups": true, + "trusted_signers": true + } + ], + "domain_name": true, + "etag": true, + "hosted_zone_id": true, + "id": true, + "in_progress_validation_batches": true, + "last_modified_time": true, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "domain_name": true, + "origin_id": true, + "s3_origin_config": [ + { + "origin_access_identity": true + } + ] + } + ], + "origin_group": [], + "restrictions": [ + { + "geo_restriction": [ + { + "locations": true + } + ] + } + ], + "status": true, + "tags_all": true, + "trusted_key_groups": true, + "trusted_signers": true, + "viewer_certificate": [ + {} + ] + } + } + }, + { + "address": "aws_cloudfront_distribution.redirect_to_https", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "redirect_to_https", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "aliases": null, + "comment": null, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + "GET", + "HEAD" + ], + "cache_policy_id": null, + "cached_methods": [ + "GET", + "HEAD" + ], + "compress": false, + "field_level_encryption_id": null, + "forwarded_values": [ + { + "cookies": [ + { + "forward": "none" + } + ], + "query_string": false + } + ], + "lambda_function_association": [], + "min_ttl": 0, + "origin_request_policy_id": null, + "realtime_log_config_arn": null, + "smooth_streaming": null, + "viewer_protocol_policy": "redirect-to-https" + } + ], + "default_root_object": null, + "enabled": true, + "http_version": "http2", + "is_ipv6_enabled": false, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "origin_path": "", + "s3_origin_config": [ + {} + ] + } + ], + "origin_group": [], + "price_class": "PriceClass_All", + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": "none" + } + ] + } + ], + "retain_on_delete": false, + "tags": null, + "viewer_certificate": [ + { + "acm_certificate_arn": null, + "cloudfront_default_certificate": true, + "iam_certificate_id": null, + "minimum_protocol_version": "TLSv1", + "ssl_support_method": null + } + ], + "wait_for_deployment": true, + "web_acl_id": null + }, + "after_unknown": { + "arn": true, + "caller_reference": true, + "custom_error_response": [], + "default_cache_behavior": [ + { + "allowed_methods": [ + false, + false + ], + "cached_methods": [ + false, + false + ], + "default_ttl": true, + "forwarded_values": [ + { + "cookies": [ + { + "whitelisted_names": true + } + ], + "headers": true, + "query_string_cache_keys": true + } + ], + "lambda_function_association": [], + "max_ttl": true, + "target_origin_id": true, + "trusted_key_groups": true, + "trusted_signers": true + } + ], + "domain_name": true, + "etag": true, + "hosted_zone_id": true, + "id": true, + "in_progress_validation_batches": true, + "last_modified_time": true, + "logging_config": [], + "ordered_cache_behavior": [], + "origin": [ + { + "custom_header": [], + "custom_origin_config": [], + "domain_name": true, + "origin_id": true, + "s3_origin_config": [ + { + "origin_access_identity": true + } + ] + } + ], + "origin_group": [], + "restrictions": [ + { + "geo_restriction": [ + { + "locations": true + } + ] + } + ], + "status": true, + "tags_all": true, + "trusted_key_groups": true, + "trusted_signers": true, + "viewer_certificate": [ + {} + ] + } + } + }, + { + "address": "aws_cloudfront_origin_access_identity.origin_access_identity", + "mode": "managed", + "type": "aws_cloudfront_origin_access_identity", + "name": "origin_access_identity", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "comment": null + }, + "after_unknown": { + "caller_reference": true, + "cloudfront_access_identity_path": true, + "etag": true, + "iam_arn": true, + "id": true, + "s3_canonical_user_id": true + } + } + }, + { + "address": "aws_s3_bucket.bucket", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "bucket", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_cloudfront_distribution.allow_all", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "allow_all", + "provider_config_key": "aws", + "expressions": { + "default_cache_behavior": [ + { + "allowed_methods": { + "constant_value": [ + "HEAD", + "GET" + ] + }, + "cached_methods": { + "constant_value": [ + "HEAD", + "GET" + ] + }, + "forwarded_values": [ + { + "cookies": [ + { + "forward": { + "constant_value": "none" + } + } + ], + "query_string": { + "constant_value": false + } + } + ], + "target_origin_id": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "viewer_protocol_policy": { + "constant_value": "allow-all" + } + } + ], + "enabled": { + "constant_value": true + }, + "origin": [ + { + "domain_name": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "origin_id": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "s3_origin_config": [ + { + "origin_access_identity": { + "references": [ + "aws_cloudfront_origin_access_identity.origin_access_identity" + ] + } + } + ] + } + ], + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": { + "constant_value": "none" + } + } + ] + } + ], + "viewer_certificate": [ + { + "cloudfront_default_certificate": { + "constant_value": true + } + } + ] + }, + "schema_version": 1 + }, + { + "address": "aws_cloudfront_distribution.https_only", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "https_only", + "provider_config_key": "aws", + "expressions": { + "default_cache_behavior": [ + { + "allowed_methods": { + "constant_value": [ + "HEAD", + "GET" + ] + }, + "cached_methods": { + "constant_value": [ + "HEAD", + "GET" + ] + }, + "forwarded_values": [ + { + "cookies": [ + { + "forward": { + "constant_value": "none" + } + } + ], + "query_string": { + "constant_value": false + } + } + ], + "target_origin_id": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "viewer_protocol_policy": { + "constant_value": "https-only" + } + } + ], + "enabled": { + "constant_value": true + }, + "origin": [ + { + "domain_name": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "origin_id": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "s3_origin_config": [ + { + "origin_access_identity": { + "references": [ + "aws_cloudfront_origin_access_identity.origin_access_identity" + ] + } + } + ] + } + ], + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": { + "constant_value": "none" + } + } + ] + } + ], + "viewer_certificate": [ + { + "cloudfront_default_certificate": { + "constant_value": true + } + } + ] + }, + "schema_version": 1 + }, + { + "address": "aws_cloudfront_distribution.redirect_to_https", + "mode": "managed", + "type": "aws_cloudfront_distribution", + "name": "redirect_to_https", + "provider_config_key": "aws", + "expressions": { + "default_cache_behavior": [ + { + "allowed_methods": { + "constant_value": [ + "HEAD", + "GET" + ] + }, + "cached_methods": { + "constant_value": [ + "HEAD", + "GET" + ] + }, + "forwarded_values": [ + { + "cookies": [ + { + "forward": { + "constant_value": "none" + } + } + ], + "query_string": { + "constant_value": false + } + } + ], + "target_origin_id": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "viewer_protocol_policy": { + "constant_value": "redirect-to-https" + } + } + ], + "enabled": { + "constant_value": true + }, + "origin": [ + { + "domain_name": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "origin_id": { + "references": [ + "aws_s3_bucket.bucket" + ] + }, + "s3_origin_config": [ + { + "origin_access_identity": { + "references": [ + "aws_cloudfront_origin_access_identity.origin_access_identity" + ] + } + } + ] + } + ], + "restrictions": [ + { + "geo_restriction": [ + { + "restriction_type": { + "constant_value": "none" + } + } + ] + } + ], + "viewer_certificate": [ + { + "cloudfront_default_certificate": { + "constant_value": true + } + } + ] + }, + "schema_version": 1 + }, + { + "address": "aws_cloudfront_origin_access_identity.origin_access_identity", + "mode": "managed", + "type": "aws_cloudfront_origin_access_identity", + "name": "origin_access_identity", + "provider_config_key": "aws", + "schema_version": 0 + }, + { + "address": "aws_s3_bucket.bucket", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "bucket", + "provider_config_key": "aws", + "expressions": { + "force_destroy": { + "constant_value": true + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego index 98048018..421e7afe 100644 --- a/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego +++ b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,385 +16,17 @@ # # tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.cloudtrail.inputs.log_file_validation_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_cloudtrail.invalid_trail", - "mode": "managed", - "type": "aws_cloudtrail", - "name": "invalid_trail", - "provider_name": "aws", - "schema_version": 0, - "values": { - "cloud_watch_logs_group_arn": null, - "cloud_watch_logs_role_arn": null, - "enable_log_file_validation": false, - "enable_logging": true, - "event_selector": [], - "include_global_service_events": true, - "insight_selector": [], - "is_multi_region_trail": false, - "is_organization_trail": false, - "kms_key_id": null, - "name": "invalid_trail", - "s3_key_prefix": null, - "sns_topic_name": null, - "tags": null - } - }, - { - "address": "aws_cloudtrail.valid_trail", - "mode": "managed", - "type": "aws_cloudtrail", - "name": "valid_trail", - "provider_name": "aws", - "schema_version": 0, - "values": { - "cloud_watch_logs_group_arn": null, - "cloud_watch_logs_role_arn": null, - "enable_log_file_validation": true, - "enable_logging": true, - "event_selector": [], - "include_global_service_events": true, - "insight_selector": [], - "is_multi_region_trail": false, - "is_organization_trail": false, - "kms_key_id": null, - "name": "valid_trail", - "s3_key_prefix": null, - "sns_topic_name": null, - "tags": null - } - }, - { - "address": "aws_s3_bucket.trail_bucket", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "trail_bucket", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - } - }, - { - "address": "aws_s3_bucket_policy.policy", - "mode": "managed", - "type": "aws_s3_bucket_policy", - "name": "policy", - "provider_name": "aws", - "schema_version": 0 - }, - { - "address": "data.aws_caller_identity.current", - "mode": "data", - "type": "aws_caller_identity", - "name": "current", - "provider_name": "aws", - "schema_version": 0 - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_cloudtrail.invalid_trail", - "mode": "managed", - "type": "aws_cloudtrail", - "name": "invalid_trail", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "cloud_watch_logs_group_arn": null, - "cloud_watch_logs_role_arn": null, - "enable_log_file_validation": false, - "enable_logging": true, - "event_selector": [], - "include_global_service_events": true, - "insight_selector": [], - "is_multi_region_trail": false, - "is_organization_trail": false, - "kms_key_id": null, - "name": "invalid_trail", - "s3_key_prefix": null, - "sns_topic_name": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "event_selector": [], - "home_region": true, - "id": true, - "insight_selector": [], - "s3_bucket_name": true - } - } - }, - { - "address": "aws_cloudtrail.valid_trail", - "mode": "managed", - "type": "aws_cloudtrail", - "name": "valid_trail", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "cloud_watch_logs_group_arn": null, - "cloud_watch_logs_role_arn": null, - "enable_log_file_validation": true, - "enable_logging": true, - "event_selector": [], - "include_global_service_events": true, - "insight_selector": [], - "is_multi_region_trail": false, - "is_organization_trail": false, - "kms_key_id": null, - "name": "valid_trail", - "s3_key_prefix": null, - "sns_topic_name": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "event_selector": [], - "home_region": true, - "id": true, - "insight_selector": [], - "s3_bucket_name": true - } - } - }, - { - "address": "aws_s3_bucket.trail_bucket", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "trail_bucket", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - }, - { - "address": "aws_s3_bucket_policy.policy", - "mode": "managed", - "type": "aws_s3_bucket_policy", - "name": "policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": {}, - "after_unknown": { - "bucket": true, - "id": true, - "policy": true - } - } - }, - { - "address": "data.aws_caller_identity.current", - "mode": "data", - "type": "aws_caller_identity", - "name": "current", - "provider_name": "aws", - "change": { - "actions": [ - "read" - ], - "before": null, - "after": {}, - "after_unknown": { - "account_id": true, - "arn": true, - "id": true, - "user_id": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_cloudtrail.invalid_trail", - "mode": "managed", - "type": "aws_cloudtrail", - "name": "invalid_trail", - "provider_config_key": "aws", - "expressions": { - "enable_log_file_validation": { - "constant_value": false - }, - "name": { - "constant_value": "invalid_trail" - }, - "s3_bucket_name": { - "references": [ - "aws_s3_bucket.trail_bucket" - ] - } - }, - "schema_version": 0, - "depends_on": [ - "aws_s3_bucket_policy.policy" - ] - }, - { - "address": "aws_cloudtrail.valid_trail", - "mode": "managed", - "type": "aws_cloudtrail", - "name": "valid_trail", - "provider_config_key": "aws", - "expressions": { - "enable_log_file_validation": { - "constant_value": true - }, - "name": { - "constant_value": "valid_trail" - }, - "s3_bucket_name": { - "references": [ - "aws_s3_bucket.trail_bucket" - ] - } - }, - "schema_version": 0, - "depends_on": [ - "aws_s3_bucket_policy.policy" - ] - }, - { - "address": "aws_s3_bucket.trail_bucket", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "trail_bucket", - "provider_config_key": "aws", - "expressions": { - "force_destroy": { - "constant_value": true - } - }, - "schema_version": 0 - }, - { - "address": "aws_s3_bucket_policy.policy", - "mode": "managed", - "type": "aws_s3_bucket_policy", - "name": "policy", - "provider_config_key": "aws", - "expressions": { - "bucket": { - "references": [ - "aws_s3_bucket.trail_bucket" - ] - }, - "policy": { - "references": [ - "aws_s3_bucket.trail_bucket", - "aws_s3_bucket.trail_bucket", - "data.aws_caller_identity.current" - ] - } - }, - "schema_version": 0 - }, - { - "address": "data.aws_caller_identity.current", - "mode": "data", - "type": "aws_caller_identity", - "name": "current", - "provider_config_key": "aws", - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("log_file_validation_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tfplan b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tfplan new file mode 100644 index 00000000..efa36db1 --- /dev/null +++ b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.tfplan @@ -0,0 +1,378 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_cloudtrail.invalid_trail", + "mode": "managed", + "type": "aws_cloudtrail", + "name": "invalid_trail", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "cloud_watch_logs_group_arn": null, + "cloud_watch_logs_role_arn": null, + "enable_log_file_validation": false, + "enable_logging": true, + "event_selector": [], + "include_global_service_events": true, + "insight_selector": [], + "is_multi_region_trail": false, + "is_organization_trail": false, + "kms_key_id": null, + "name": "invalid_trail", + "s3_key_prefix": null, + "sns_topic_name": null, + "tags": null + } + }, + { + "address": "aws_cloudtrail.valid_trail", + "mode": "managed", + "type": "aws_cloudtrail", + "name": "valid_trail", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "cloud_watch_logs_group_arn": null, + "cloud_watch_logs_role_arn": null, + "enable_log_file_validation": true, + "enable_logging": true, + "event_selector": [], + "include_global_service_events": true, + "insight_selector": [], + "is_multi_region_trail": false, + "is_organization_trail": false, + "kms_key_id": null, + "name": "valid_trail", + "s3_key_prefix": null, + "sns_topic_name": null, + "tags": null + } + }, + { + "address": "aws_s3_bucket.trail_bucket", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "trail_bucket", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + } + }, + { + "address": "aws_s3_bucket_policy.policy", + "mode": "managed", + "type": "aws_s3_bucket_policy", + "name": "policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0 + }, + { + "address": "data.aws_caller_identity.current", + "mode": "data", + "type": "aws_caller_identity", + "name": "current", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "account_id": "819995596046", + "arn": "arn:aws:iam::819995596046:user/jason", + "id": "819995596046", + "user_id": "AIDA3524L2UHGTZ7STQ2Y" + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_cloudtrail.invalid_trail", + "mode": "managed", + "type": "aws_cloudtrail", + "name": "invalid_trail", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "cloud_watch_logs_group_arn": null, + "cloud_watch_logs_role_arn": null, + "enable_log_file_validation": false, + "enable_logging": true, + "event_selector": [], + "include_global_service_events": true, + "insight_selector": [], + "is_multi_region_trail": false, + "is_organization_trail": false, + "kms_key_id": null, + "name": "invalid_trail", + "s3_key_prefix": null, + "sns_topic_name": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "event_selector": [], + "home_region": true, + "id": true, + "insight_selector": [], + "s3_bucket_name": true, + "tags_all": true + } + } + }, + { + "address": "aws_cloudtrail.valid_trail", + "mode": "managed", + "type": "aws_cloudtrail", + "name": "valid_trail", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "cloud_watch_logs_group_arn": null, + "cloud_watch_logs_role_arn": null, + "enable_log_file_validation": true, + "enable_logging": true, + "event_selector": [], + "include_global_service_events": true, + "insight_selector": [], + "is_multi_region_trail": false, + "is_organization_trail": false, + "kms_key_id": null, + "name": "valid_trail", + "s3_key_prefix": null, + "sns_topic_name": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "event_selector": [], + "home_region": true, + "id": true, + "insight_selector": [], + "s3_bucket_name": true, + "tags_all": true + } + } + }, + { + "address": "aws_s3_bucket.trail_bucket", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "trail_bucket", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + }, + { + "address": "aws_s3_bucket_policy.policy", + "mode": "managed", + "type": "aws_s3_bucket_policy", + "name": "policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": {}, + "after_unknown": { + "bucket": true, + "id": true, + "policy": true + } + } + }, + { + "address": "data.aws_caller_identity.current", + "mode": "data", + "type": "aws_caller_identity", + "name": "current", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "read" + ], + "before": null, + "after": { + "account_id": "819995596046", + "arn": "arn:aws:iam::819995596046:user/jason", + "id": "819995596046", + "user_id": "AIDA3524L2UHGTZ7STQ2Y" + }, + "after_unknown": {} + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_cloudtrail.invalid_trail", + "mode": "managed", + "type": "aws_cloudtrail", + "name": "invalid_trail", + "provider_config_key": "aws", + "expressions": { + "enable_log_file_validation": { + "constant_value": false + }, + "name": { + "constant_value": "invalid_trail" + }, + "s3_bucket_name": { + "references": [ + "aws_s3_bucket.trail_bucket" + ] + } + }, + "schema_version": 0, + "depends_on": [ + "aws_s3_bucket_policy.policy" + ] + }, + { + "address": "aws_cloudtrail.valid_trail", + "mode": "managed", + "type": "aws_cloudtrail", + "name": "valid_trail", + "provider_config_key": "aws", + "expressions": { + "enable_log_file_validation": { + "constant_value": true + }, + "name": { + "constant_value": "valid_trail" + }, + "s3_bucket_name": { + "references": [ + "aws_s3_bucket.trail_bucket" + ] + } + }, + "schema_version": 0, + "depends_on": [ + "aws_s3_bucket_policy.policy" + ] + }, + { + "address": "aws_s3_bucket.trail_bucket", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "trail_bucket", + "provider_config_key": "aws", + "expressions": { + "force_destroy": { + "constant_value": true + } + }, + "schema_version": 0 + }, + { + "address": "aws_s3_bucket_policy.policy", + "mode": "managed", + "type": "aws_s3_bucket_policy", + "name": "policy", + "provider_config_key": "aws", + "expressions": { + "bucket": { + "references": [ + "aws_s3_bucket.trail_bucket" + ] + }, + "policy": { + "references": [ + "aws_s3_bucket.trail_bucket", + "aws_s3_bucket.trail_bucket", + "data.aws_caller_identity.current" + ] + } + }, + "schema_version": 0 + }, + { + "address": "data.aws_caller_identity.current", + "mode": "data", + "type": "aws_caller_identity", + "name": "current", + "provider_config_key": "aws", + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego index 710dc21e..fb1db2bf 100644 --- a/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego +++ b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,232 +16,17 @@ # # tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.ebs.inputs.volume_encrypted_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_ebs_volume.bad", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "bad", - "provider_name": "aws", - "schema_version": 0, - "values": { - "availability_zone": "us-west-2a", - "encrypted": false, - "multi_attach_enabled": null, - "outpost_arn": null, - "size": 40, - "tags": null - } - }, - { - "address": "aws_ebs_volume.good", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "good", - "provider_name": "aws", - "schema_version": 0, - "values": { - "availability_zone": "us-west-2a", - "encrypted": true, - "multi_attach_enabled": null, - "outpost_arn": null, - "size": 40, - "tags": null - } - }, - { - "address": "aws_ebs_volume.missing", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "missing", - "provider_name": "aws", - "schema_version": 0, - "values": { - "availability_zone": "us-west-2a", - "multi_attach_enabled": null, - "outpost_arn": null, - "size": 40, - "tags": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_ebs_volume.bad", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "bad", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "availability_zone": "us-west-2a", - "encrypted": false, - "multi_attach_enabled": null, - "outpost_arn": null, - "size": 40, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "iops": true, - "kms_key_id": true, - "snapshot_id": true, - "type": true - } - } - }, - { - "address": "aws_ebs_volume.good", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "good", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "availability_zone": "us-west-2a", - "encrypted": true, - "multi_attach_enabled": null, - "outpost_arn": null, - "size": 40, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "iops": true, - "kms_key_id": true, - "snapshot_id": true, - "type": true - } - } - }, - { - "address": "aws_ebs_volume.missing", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "missing", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "availability_zone": "us-west-2a", - "multi_attach_enabled": null, - "outpost_arn": null, - "size": 40, - "tags": null - }, - "after_unknown": { - "arn": true, - "encrypted": true, - "id": true, - "iops": true, - "kms_key_id": true, - "snapshot_id": true, - "type": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_ebs_volume.bad", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "bad", - "provider_config_key": "aws", - "expressions": { - "availability_zone": { - "constant_value": "us-west-2a" - }, - "encrypted": { - "constant_value": false - }, - "size": { - "constant_value": 40 - } - }, - "schema_version": 0 - }, - { - "address": "aws_ebs_volume.good", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "good", - "provider_config_key": "aws", - "expressions": { - "availability_zone": { - "constant_value": "us-west-2a" - }, - "encrypted": { - "constant_value": true - }, - "size": { - "constant_value": 40 - } - }, - "schema_version": 0 - }, - { - "address": "aws_ebs_volume.missing", - "mode": "managed", - "type": "aws_ebs_volume", - "name": "missing", - "provider_config_key": "aws", - "expressions": { - "availability_zone": { - "constant_value": "us-west-2a" - }, - "size": { - "constant_value": 40 - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("volume_encrypted_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tfplan b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tfplan new file mode 100644 index 00000000..f58fa542 --- /dev/null +++ b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.tfplan @@ -0,0 +1,222 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ebs_volume.bad", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "bad", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "availability_zone": "us-west-2a", + "encrypted": false, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + } + }, + { + "address": "aws_ebs_volume.good", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "good", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "availability_zone": "us-west-2a", + "encrypted": true, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + } + }, + { + "address": "aws_ebs_volume.missing", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "missing", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "availability_zone": "us-west-2a", + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ebs_volume.bad", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "bad", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "availability_zone": "us-west-2a", + "encrypted": false, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "iops": true, + "kms_key_id": true, + "snapshot_id": true, + "tags_all": true, + "throughput": true, + "type": true + } + } + }, + { + "address": "aws_ebs_volume.good", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "good", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "availability_zone": "us-west-2a", + "encrypted": true, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "iops": true, + "kms_key_id": true, + "snapshot_id": true, + "tags_all": true, + "throughput": true, + "type": true + } + } + }, + { + "address": "aws_ebs_volume.missing", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "missing", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "availability_zone": "us-west-2a", + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + }, + "after_unknown": { + "arn": true, + "encrypted": true, + "id": true, + "iops": true, + "kms_key_id": true, + "snapshot_id": true, + "tags_all": true, + "throughput": true, + "type": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ebs_volume.bad", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "bad", + "provider_config_key": "aws", + "expressions": { + "availability_zone": { + "constant_value": "us-west-2a" + }, + "encrypted": { + "constant_value": false + }, + "size": { + "constant_value": 40 + } + }, + "schema_version": 0 + }, + { + "address": "aws_ebs_volume.good", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "good", + "provider_config_key": "aws", + "expressions": { + "availability_zone": { + "constant_value": "us-west-2a" + }, + "encrypted": { + "constant_value": true + }, + "size": { + "constant_value": 40 + } + }, + "schema_version": 0 + }, + { + "address": "aws_ebs_volume.missing", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "missing", + "provider_config_key": "aws", + "expressions": { + "availability_zone": { + "constant_value": "us-west-2a" + }, + "size": { + "constant_value": 40 + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego index b88173ec..3b7edcde 100644 --- a/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego +++ b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,676 +16,17 @@ # # tests/rules/tf/aws/iam/inputs/admin_policy_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.iam.inputs.admin_policy_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_iam_group.my_group", - "mode": "managed", - "type": "aws_iam_group", - "name": "my_group", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "my_group", - "path": "/users/" - } - }, - { - "address": "aws_iam_group_policy.invalid_group_policy", - "mode": "managed", - "type": "aws_iam_group_policy", - "name": "invalid_group_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "invalid_group_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_group_policy.valid_group_policy", - "mode": "managed", - "type": "aws_iam_group_policy", - "name": "valid_group_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "valid_group_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy.invalid_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Invalid policy", - "name": "test_invalid_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy.valid_deny_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_deny_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Valid deny policy", - "name": "test_valid_deny_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_role.my_test_role", - "mode": "managed", - "type": "aws_iam_role", - "name": "my_test_role", - "provider_name": "aws", - "schema_version": 0, - "values": { - "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", - "description": null, - "force_detach_policies": false, - "max_session_duration": 3600, - "name": "my_test_role", - "name_prefix": null, - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_role_policy.invalid_role_policy", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "invalid_role_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "invalid_role_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_role_policy.valid_role_policy", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "valid_role_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "valid_role_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_user.my_test_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "my_test_user", - "provider_name": "aws", - "schema_version": 0, - "values": { - "force_destroy": false, - "name": "my_test_user", - "path": "/system/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user_policy.invalid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "invalid_user_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "invalid_user_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", - "user": "my_test_user" - } - }, - { - "address": "aws_iam_user_policy.valid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "valid_user_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "valid_user_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", - "user": "my_test_user" - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_iam_group.my_group", - "mode": "managed", - "type": "aws_iam_group", - "name": "my_group", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "my_group", - "path": "/users/" - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_group_policy.invalid_group_policy", - "mode": "managed", - "type": "aws_iam_group_policy", - "name": "invalid_group_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "invalid_group_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "group": true, - "id": true - } - } - }, - { - "address": "aws_iam_group_policy.valid_group_policy", - "mode": "managed", - "type": "aws_iam_group_policy", - "name": "valid_group_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "valid_group_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "group": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy.invalid_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Invalid policy", - "name": "test_invalid_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy.valid_deny_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_deny_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Valid deny policy", - "name": "test_valid_deny_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_role.my_test_role", - "mode": "managed", - "type": "aws_iam_role", - "name": "my_test_role", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", - "description": null, - "force_detach_policies": false, - "max_session_duration": 3600, - "name": "my_test_role", - "name_prefix": null, - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "create_date": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_role_policy.invalid_role_policy", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "invalid_role_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "invalid_role_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "id": true, - "role": true - } - } - }, - { - "address": "aws_iam_role_policy.valid_role_policy", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "valid_role_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "valid_role_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "id": true, - "role": true - } - } - }, - { - "address": "aws_iam_user.my_test_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "my_test_user", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "force_destroy": false, - "name": "my_test_user", - "path": "/system/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user_policy.invalid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "invalid_user_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "invalid_user_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", - "user": "my_test_user" - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "aws_iam_user_policy.valid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "valid_user_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "valid_user_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", - "user": "my_test_user" - }, - "after_unknown": { - "id": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "version_constraint": "~> 2.41", - "expressions": { - "region": { - "constant_value": "us-west-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_iam_group.my_group", - "mode": "managed", - "type": "aws_iam_group", - "name": "my_group", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "my_group" - }, - "path": { - "constant_value": "/users/" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_group_policy.invalid_group_policy", - "mode": "managed", - "type": "aws_iam_group_policy", - "name": "invalid_group_policy", - "provider_config_key": "aws", - "expressions": { - "group": { - "references": [ - "aws_iam_group.my_group" - ] - }, - "name": { - "constant_value": "invalid_group_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_group_policy.valid_group_policy", - "mode": "managed", - "type": "aws_iam_group_policy", - "name": "valid_group_policy", - "provider_config_key": "aws", - "expressions": { - "group": { - "references": [ - "aws_iam_group.my_group" - ] - }, - "name": { - "constant_value": "valid_group_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.invalid_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Invalid policy" - }, - "name": { - "constant_value": "test_invalid_policy" - }, - "path": { - "constant_value": "/" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.valid_deny_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_deny_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Valid deny policy" - }, - "name": { - "constant_value": "test_valid_deny_policy" - }, - "path": { - "constant_value": "/" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_role.my_test_role", - "mode": "managed", - "type": "aws_iam_role", - "name": "my_test_role", - "provider_config_key": "aws", - "expressions": { - "assume_role_policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n" - }, - "name": { - "constant_value": "my_test_role" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_role_policy.invalid_role_policy", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "invalid_role_policy", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_role_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "role": { - "references": [ - "aws_iam_role.my_test_role" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_role_policy.valid_role_policy", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "valid_role_policy", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_role_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "role": { - "references": [ - "aws_iam_role.my_test_role" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user.my_test_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "my_test_user", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "my_test_user" - }, - "path": { - "constant_value": "/system/" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user_policy.invalid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "invalid_user_policy", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_user_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "user": { - "references": [ - "aws_iam_user.my_test_user" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user_policy.valid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "valid_user_policy", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_user_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "user": { - "references": [ - "aws_iam_user.my_test_user" - ] - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("admin_policy_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.tfplan b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.tfplan new file mode 100644 index 00000000..a8668ccc --- /dev/null +++ b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.tfplan @@ -0,0 +1,660 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_group.my_group", + "mode": "managed", + "type": "aws_iam_group", + "name": "my_group", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "my_group", + "path": "/users/" + } + }, + { + "address": "aws_iam_group_policy.invalid_group_policy", + "mode": "managed", + "type": "aws_iam_group_policy", + "name": "invalid_group_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "invalid_group_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + { + "address": "aws_iam_group_policy.valid_group_policy", + "mode": "managed", + "type": "aws_iam_group_policy", + "name": "valid_group_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "valid_group_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + { + "address": "aws_iam_policy.invalid_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Invalid policy", + "name": "test_invalid_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + { + "address": "aws_iam_policy.valid_deny_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_deny_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Valid deny policy", + "name": "test_valid_deny_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + { + "address": "aws_iam_role.my_test_role", + "mode": "managed", + "type": "aws_iam_role", + "name": "my_test_role", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "my_test_role", + "name_prefix": null, + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_role_policy.invalid_role_policy", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "invalid_role_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "invalid_role_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + { + "address": "aws_iam_role_policy.valid_role_policy", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "valid_role_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "valid_role_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + { + "address": "aws_iam_user.my_test_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "my_test_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "force_destroy": false, + "name": "my_test_user", + "path": "/system/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user_policy.invalid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "invalid_user_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "invalid_user_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "user": "my_test_user" + } + }, + { + "address": "aws_iam_user_policy.valid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "valid_user_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "valid_user_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "user": "my_test_user" + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_group.my_group", + "mode": "managed", + "type": "aws_iam_group", + "name": "my_group", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "my_group", + "path": "/users/" + }, + "after_unknown": { + "arn": true, + "id": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_group_policy.invalid_group_policy", + "mode": "managed", + "type": "aws_iam_group_policy", + "name": "invalid_group_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "invalid_group_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "after_unknown": { + "group": true, + "id": true + } + } + }, + { + "address": "aws_iam_group_policy.valid_group_policy", + "mode": "managed", + "type": "aws_iam_group_policy", + "name": "valid_group_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "valid_group_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "after_unknown": { + "group": true, + "id": true + } + } + }, + { + "address": "aws_iam_policy.invalid_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Invalid policy", + "name": "test_invalid_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "after_unknown": { + "arn": true, + "id": true + } + } + }, + { + "address": "aws_iam_policy.valid_deny_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_deny_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Valid deny policy", + "name": "test_valid_deny_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "after_unknown": { + "arn": true, + "id": true + } + } + }, + { + "address": "aws_iam_role.my_test_role", + "mode": "managed", + "type": "aws_iam_role", + "name": "my_test_role", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "my_test_role", + "name_prefix": null, + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_role_policy.invalid_role_policy", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "invalid_role_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "invalid_role_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "after_unknown": { + "id": true, + "role": true + } + } + }, + { + "address": "aws_iam_role_policy.valid_role_policy", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "valid_role_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "valid_role_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "after_unknown": { + "id": true, + "role": true + } + } + }, + { + "address": "aws_iam_user.my_test_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "my_test_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "force_destroy": false, + "name": "my_test_user", + "path": "/system/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user_policy.invalid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "invalid_user_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "invalid_user_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "user": "my_test_user" + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "aws_iam_user_policy.valid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "valid_user_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "valid_user_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "user": "my_test_user" + }, + "after_unknown": { + "id": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "version_constraint": "~> 2.41", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_group.my_group", + "mode": "managed", + "type": "aws_iam_group", + "name": "my_group", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "my_group" + }, + "path": { + "constant_value": "/users/" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_group_policy.invalid_group_policy", + "mode": "managed", + "type": "aws_iam_group_policy", + "name": "invalid_group_policy", + "provider_config_key": "aws", + "expressions": { + "group": { + "references": [ + "aws_iam_group.my_group" + ] + }, + "name": { + "constant_value": "invalid_group_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_group_policy.valid_group_policy", + "mode": "managed", + "type": "aws_iam_group_policy", + "name": "valid_group_policy", + "provider_config_key": "aws", + "expressions": { + "group": { + "references": [ + "aws_iam_group.my_group" + ] + }, + "name": { + "constant_value": "valid_group_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.invalid_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Invalid policy" + }, + "name": { + "constant_value": "test_invalid_policy" + }, + "path": { + "constant_value": "/" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.valid_deny_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_deny_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Valid deny policy" + }, + "name": { + "constant_value": "test_valid_deny_policy" + }, + "path": { + "constant_value": "/" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role.my_test_role", + "mode": "managed", + "type": "aws_iam_role", + "name": "my_test_role", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n" + }, + "name": { + "constant_value": "my_test_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy.invalid_role_policy", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "invalid_role_policy", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_role_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "role": { + "references": [ + "aws_iam_role.my_test_role" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy.valid_role_policy", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "valid_role_policy", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_role_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "role": { + "references": [ + "aws_iam_role.my_test_role" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user.my_test_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "my_test_user", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "my_test_user" + }, + "path": { + "constant_value": "/system/" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user_policy.invalid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "invalid_user_policy", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_user_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "user": { + "references": [ + "aws_iam_user.my_test_user" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user_policy.valid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "valid_user_policy", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_user_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "user": { + "references": [ + "aws_iam_user.my_test_user" + ] + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego index f5c56a55..b62e7016 100644 --- a/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego +++ b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,1566 +16,17 @@ # # tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.iam.inputs.user_attached_policy_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_iam_group.valid_group_blank_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_blank_users", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "valid_group_blank_users", - "path": "/" - } - }, - { - "address": "aws_iam_group.valid_group_empty_list_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_empty_list_users", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "valid_group_empty_list_users", - "path": "/" - } - }, - { - "address": "aws_iam_group.valid_group_missing_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_missing_users", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "valid_group_missing_users", - "path": "/" - } - }, - { - "address": "aws_iam_group_membership.valid_group_blank_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_blank_users_membership", - "provider_name": "aws", - "schema_version": 0, - "values": { - "group": "valid_group_blank_users", - "name": "valid_group_blank_users_membership", - "users": [ - "valid_group_blank_user" - ] - } - }, - { - "address": "aws_iam_group_membership.valid_group_empty_list_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_empty_list_users_membership", - "provider_name": "aws", - "schema_version": 0, - "values": { - "group": "valid_group_empty_list_users", - "name": "valid_group_empty_list_users_membership", - "users": [ - "valid_group_empty_list_user" - ] - } - }, - { - "address": "aws_iam_group_membership.valid_group_missing_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_missing_users_membership", - "provider_name": "aws", - "schema_version": 0, - "values": { - "group": "valid_group_missing_users", - "name": "valid_group_missing_users_membership", - "users": [ - "valid_group_missing_user" - ] - } - }, - { - "address": "aws_iam_policy.invalid_normal_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_normal_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Invalid normal policy attached to user", - "name": "invalid_normal_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy.invalid_user_policy_attachment_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_user_policy_attachment_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Invalid user policy attachment policy", - "name": "invalid_user_policy_attachment_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy.valid_group_blank_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_blank_users_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Valid group blank users policy", - "name": "valid_group_blank_users_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy.valid_group_empty_list_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_empty_list_users_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Valid group empty list users policy", - "name": "valid_group_empty_list_users_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy.valid_group_missing_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_missing_users_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Valid group missing users policy", - "name": "valid_group_missing_users_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy.valid_role_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_role_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": "Valid role policy", - "name": "valid_role_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_iam_policy_attachment.invalid_normal_policy_attachment", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "invalid_normal_policy_attachment", - "provider_name": "aws", - "schema_version": 0, - "values": { - "groups": null, - "name": "invalid_normal_policy_attachment", - "roles": null, - "users": [ - "invalid_normal_policy_user" - ] - } - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_blank_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_blank_users", - "provider_name": "aws", - "schema_version": 0, - "values": { - "groups": [ - "valid_group_blank_users" - ], - "name": "valid_group_policy_attachment_blank_users", - "roles": null, - "users": [ - "" - ] - } - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_empty_list_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_empty_list_users", - "provider_name": "aws", - "schema_version": 0, - "values": { - "groups": [ - "valid_group_empty_list_users" - ], - "name": "valid_group_policy_attachment_empty_list_users", - "roles": null, - "users": null - } - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_missing_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_missing_users", - "provider_name": "aws", - "schema_version": 0, - "values": { - "groups": [ - "valid_group_missing_users" - ], - "name": "valid_group_policy_attachment_missing_users", - "roles": null, - "users": null - } - }, - { - "address": "aws_iam_policy_attachment.valid_role_policy_attachment", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_role_policy_attachment", - "provider_name": "aws", - "schema_version": 0, - "values": { - "groups": null, - "name": "valid_role_policy_attachment", - "roles": [ - "valid_role" - ], - "users": null - } - }, - { - "address": "aws_iam_role.valid_role", - "mode": "managed", - "type": "aws_iam_role", - "name": "valid_role", - "provider_name": "aws", - "schema_version": 0, - "values": { - "assume_role_policy": "{\n\"Version\": \"2012-10-17\",\n\"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", - "description": null, - "force_detach_policies": false, - "max_session_duration": 3600, - "name": "valid_role", - "name_prefix": null, - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user.invalid_normal_policy_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_normal_policy_user", - "provider_name": "aws", - "schema_version": 0, - "values": { - "force_destroy": false, - "name": "invalid_normal_policy_user", - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user.invalid_user_policy_attachment_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_user_policy_attachment_user", - "provider_name": "aws", - "schema_version": 0, - "values": { - "force_destroy": false, - "name": "invalid_user_policy_attachment_user", - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user.invalid_user_policy_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_user_policy_user", - "provider_name": "aws", - "schema_version": 0, - "values": { - "force_destroy": false, - "name": "invalid_user_policy_user", - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user.valid_group_blank_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_blank_user", - "provider_name": "aws", - "schema_version": 0, - "values": { - "force_destroy": false, - "name": "valid_group_blank_user", - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user.valid_group_empty_list_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_empty_list_user", - "provider_name": "aws", - "schema_version": 0, - "values": { - "force_destroy": false, - "name": "valid_group_empty_list_user", - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user.valid_group_missing_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_missing_user", - "provider_name": "aws", - "schema_version": 0, - "values": { - "force_destroy": false, - "name": "valid_group_missing_user", - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_user_policy.invalid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "invalid_user_policy", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "invalid_user_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", - "user": "invalid_user_policy_user" - } - }, - { - "address": "aws_iam_user_policy_attachment.invalid_user_policy_attachment", - "mode": "managed", - "type": "aws_iam_user_policy_attachment", - "name": "invalid_user_policy_attachment", - "provider_name": "aws", - "schema_version": 0, - "values": { - "user": "invalid_user_policy_attachment_user" - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_iam_group.valid_group_blank_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_blank_users", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "valid_group_blank_users", - "path": "/" - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_group.valid_group_empty_list_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_empty_list_users", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "valid_group_empty_list_users", - "path": "/" - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_group.valid_group_missing_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_missing_users", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "valid_group_missing_users", - "path": "/" - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_group_membership.valid_group_blank_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_blank_users_membership", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "group": "valid_group_blank_users", - "name": "valid_group_blank_users_membership", - "users": [ - "valid_group_blank_user" - ] - }, - "after_unknown": { - "id": true, - "users": [ - false - ] - } - } - }, - { - "address": "aws_iam_group_membership.valid_group_empty_list_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_empty_list_users_membership", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "group": "valid_group_empty_list_users", - "name": "valid_group_empty_list_users_membership", - "users": [ - "valid_group_empty_list_user" - ] - }, - "after_unknown": { - "id": true, - "users": [ - false - ] - } - } - }, - { - "address": "aws_iam_group_membership.valid_group_missing_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_missing_users_membership", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "group": "valid_group_missing_users", - "name": "valid_group_missing_users_membership", - "users": [ - "valid_group_missing_user" - ] - }, - "after_unknown": { - "id": true, - "users": [ - false - ] - } - } - }, - { - "address": "aws_iam_policy.invalid_normal_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_normal_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Invalid normal policy attached to user", - "name": "invalid_normal_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy.invalid_user_policy_attachment_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_user_policy_attachment_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Invalid user policy attachment policy", - "name": "invalid_user_policy_attachment_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy.valid_group_blank_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_blank_users_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Valid group blank users policy", - "name": "valid_group_blank_users_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy.valid_group_empty_list_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_empty_list_users_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Valid group empty list users policy", - "name": "valid_group_empty_list_users_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy.valid_group_missing_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_missing_users_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Valid group missing users policy", - "name": "valid_group_missing_users_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy.valid_role_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_role_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Valid role policy", - "name": "valid_role_policy", - "name_prefix": null, - "path": "/", - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_iam_policy_attachment.invalid_normal_policy_attachment", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "invalid_normal_policy_attachment", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "groups": null, - "name": "invalid_normal_policy_attachment", - "roles": null, - "users": [ - "invalid_normal_policy_user" - ] - }, - "after_unknown": { - "id": true, - "policy_arn": true, - "users": [ - false - ] - } - } - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_blank_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_blank_users", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "groups": [ - "valid_group_blank_users" - ], - "name": "valid_group_policy_attachment_blank_users", - "roles": null, - "users": [ - "" - ] - }, - "after_unknown": { - "groups": [ - false - ], - "id": true, - "policy_arn": true, - "users": [ - false - ] - } - } - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_empty_list_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_empty_list_users", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "groups": [ - "valid_group_empty_list_users" - ], - "name": "valid_group_policy_attachment_empty_list_users", - "roles": null, - "users": null - }, - "after_unknown": { - "groups": [ - false - ], - "id": true, - "policy_arn": true - } - } - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_missing_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_missing_users", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "groups": [ - "valid_group_missing_users" - ], - "name": "valid_group_policy_attachment_missing_users", - "roles": null, - "users": null - }, - "after_unknown": { - "groups": [ - false - ], - "id": true, - "policy_arn": true - } - } - }, - { - "address": "aws_iam_policy_attachment.valid_role_policy_attachment", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_role_policy_attachment", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "groups": null, - "name": "valid_role_policy_attachment", - "roles": [ - "valid_role" - ], - "users": null - }, - "after_unknown": { - "id": true, - "policy_arn": true, - "roles": [ - false - ] - } - } - }, - { - "address": "aws_iam_role.valid_role", - "mode": "managed", - "type": "aws_iam_role", - "name": "valid_role", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assume_role_policy": "{\n\"Version\": \"2012-10-17\",\n\"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", - "description": null, - "force_detach_policies": false, - "max_session_duration": 3600, - "name": "valid_role", - "name_prefix": null, - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "create_date": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user.invalid_normal_policy_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_normal_policy_user", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "force_destroy": false, - "name": "invalid_normal_policy_user", - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user.invalid_user_policy_attachment_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_user_policy_attachment_user", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "force_destroy": false, - "name": "invalid_user_policy_attachment_user", - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user.invalid_user_policy_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_user_policy_user", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "force_destroy": false, - "name": "invalid_user_policy_user", - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user.valid_group_blank_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_blank_user", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "force_destroy": false, - "name": "valid_group_blank_user", - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user.valid_group_empty_list_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_empty_list_user", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "force_destroy": false, - "name": "valid_group_empty_list_user", - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user.valid_group_missing_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_missing_user", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "force_destroy": false, - "name": "valid_group_missing_user", - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_user_policy.invalid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "invalid_user_policy", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "invalid_user_policy", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", - "user": "invalid_user_policy_user" - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "aws_iam_user_policy_attachment.invalid_user_policy_attachment", - "mode": "managed", - "type": "aws_iam_user_policy_attachment", - "name": "invalid_user_policy_attachment", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "user": "invalid_user_policy_attachment_user" - }, - "after_unknown": { - "id": true, - "policy_arn": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-west-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_iam_group.valid_group_blank_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_blank_users", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_group_blank_users" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_group.valid_group_empty_list_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_empty_list_users", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_group_empty_list_users" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_group.valid_group_missing_users", - "mode": "managed", - "type": "aws_iam_group", - "name": "valid_group_missing_users", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_group_missing_users" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_group_membership.valid_group_blank_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_blank_users_membership", - "provider_config_key": "aws", - "expressions": { - "group": { - "references": [ - "aws_iam_group.valid_group_blank_users" - ] - }, - "name": { - "constant_value": "valid_group_blank_users_membership" - }, - "users": { - "references": [ - "aws_iam_user.valid_group_blank_user" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_group_membership.valid_group_empty_list_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_empty_list_users_membership", - "provider_config_key": "aws", - "expressions": { - "group": { - "references": [ - "aws_iam_group.valid_group_empty_list_users" - ] - }, - "name": { - "constant_value": "valid_group_empty_list_users_membership" - }, - "users": { - "references": [ - "aws_iam_user.valid_group_empty_list_user" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_group_membership.valid_group_missing_users_membership", - "mode": "managed", - "type": "aws_iam_group_membership", - "name": "valid_group_missing_users_membership", - "provider_config_key": "aws", - "expressions": { - "group": { - "references": [ - "aws_iam_group.valid_group_missing_users" - ] - }, - "name": { - "constant_value": "valid_group_missing_users_membership" - }, - "users": { - "references": [ - "aws_iam_user.valid_group_missing_user" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.invalid_normal_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_normal_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Invalid normal policy attached to user" - }, - "name": { - "constant_value": "invalid_normal_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.invalid_user_policy_attachment_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "invalid_user_policy_attachment_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Invalid user policy attachment policy" - }, - "name": { - "constant_value": "invalid_user_policy_attachment_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.valid_group_blank_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_blank_users_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Valid group blank users policy" - }, - "name": { - "constant_value": "valid_group_blank_users_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.valid_group_empty_list_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_empty_list_users_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Valid group empty list users policy" - }, - "name": { - "constant_value": "valid_group_empty_list_users_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.valid_group_missing_users_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_group_missing_users_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Valid group missing users policy" - }, - "name": { - "constant_value": "valid_group_missing_users_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy.valid_role_policy", - "mode": "managed", - "type": "aws_iam_policy", - "name": "valid_role_policy", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "Valid role policy" - }, - "name": { - "constant_value": "valid_role_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy_attachment.invalid_normal_policy_attachment", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "invalid_normal_policy_attachment", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_normal_policy_attachment" - }, - "policy_arn": { - "references": [ - "aws_iam_policy.invalid_normal_policy" - ] - }, - "users": { - "references": [ - "aws_iam_user.invalid_normal_policy_user" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_blank_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_blank_users", - "provider_config_key": "aws", - "expressions": { - "groups": { - "references": [ - "aws_iam_group.valid_group_blank_users" - ] - }, - "name": { - "constant_value": "valid_group_policy_attachment_blank_users" - }, - "policy_arn": { - "references": [ - "aws_iam_policy.valid_group_blank_users_policy" - ] - }, - "users": { - "constant_value": [ - "" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_empty_list_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_empty_list_users", - "provider_config_key": "aws", - "expressions": { - "groups": { - "references": [ - "aws_iam_group.valid_group_empty_list_users" - ] - }, - "name": { - "constant_value": "valid_group_policy_attachment_empty_list_users" - }, - "policy_arn": { - "references": [ - "aws_iam_policy.valid_group_empty_list_users_policy" - ] - }, - "users": { - "constant_value": [] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy_attachment.valid_group_policy_attachment_missing_users", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_group_policy_attachment_missing_users", - "provider_config_key": "aws", - "expressions": { - "groups": { - "references": [ - "aws_iam_group.valid_group_missing_users" - ] - }, - "name": { - "constant_value": "valid_group_policy_attachment_missing_users" - }, - "policy_arn": { - "references": [ - "aws_iam_policy.valid_group_missing_users_policy" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_policy_attachment.valid_role_policy_attachment", - "mode": "managed", - "type": "aws_iam_policy_attachment", - "name": "valid_role_policy_attachment", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_role_policy_attachment" - }, - "policy_arn": { - "references": [ - "aws_iam_policy.valid_role_policy" - ] - }, - "roles": { - "references": [ - "aws_iam_role.valid_role" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_role.valid_role", - "mode": "managed", - "type": "aws_iam_role", - "name": "valid_role", - "provider_config_key": "aws", - "expressions": { - "assume_role_policy": { - "constant_value": "{\n\"Version\": \"2012-10-17\",\n\"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n" - }, - "name": { - "constant_value": "valid_role" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user.invalid_normal_policy_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_normal_policy_user", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_normal_policy_user" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user.invalid_user_policy_attachment_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_user_policy_attachment_user", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_user_policy_attachment_user" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user.invalid_user_policy_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "invalid_user_policy_user", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_user_policy_user" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user.valid_group_blank_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_blank_user", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_group_blank_user" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user.valid_group_empty_list_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_empty_list_user", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_group_empty_list_user" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user.valid_group_missing_user", - "mode": "managed", - "type": "aws_iam_user", - "name": "valid_group_missing_user", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_group_missing_user" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user_policy.invalid_user_policy", - "mode": "managed", - "type": "aws_iam_user_policy", - "name": "invalid_user_policy", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_user_policy" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "user": { - "references": [ - "aws_iam_user.invalid_user_policy_user" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_user_policy_attachment.invalid_user_policy_attachment", - "mode": "managed", - "type": "aws_iam_user_policy_attachment", - "name": "invalid_user_policy_attachment", - "provider_config_key": "aws", - "expressions": { - "policy_arn": { - "references": [ - "aws_iam_policy.invalid_user_policy_attachment_policy" - ] - }, - "user": { - "references": [ - "aws_iam_user.invalid_user_policy_attachment_user" - ] - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("user_attached_policy_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tfplan b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tfplan new file mode 100644 index 00000000..7a251a62 --- /dev/null +++ b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.tfplan @@ -0,0 +1,1583 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_group.valid_group_blank_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_blank_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "valid_group_blank_users", + "path": "/" + } + }, + { + "address": "aws_iam_group.valid_group_empty_list_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_empty_list_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "valid_group_empty_list_users", + "path": "/" + } + }, + { + "address": "aws_iam_group.valid_group_missing_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_missing_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "valid_group_missing_users", + "path": "/" + } + }, + { + "address": "aws_iam_group_membership.valid_group_blank_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_blank_users_membership", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "group": "valid_group_blank_users", + "name": "valid_group_blank_users_membership", + "users": [ + "valid_group_blank_user" + ] + } + }, + { + "address": "aws_iam_group_membership.valid_group_empty_list_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_empty_list_users_membership", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "group": "valid_group_empty_list_users", + "name": "valid_group_empty_list_users_membership", + "users": [ + "valid_group_empty_list_user" + ] + } + }, + { + "address": "aws_iam_group_membership.valid_group_missing_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_missing_users_membership", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "group": "valid_group_missing_users", + "name": "valid_group_missing_users_membership", + "users": [ + "valid_group_missing_user" + ] + } + }, + { + "address": "aws_iam_policy.invalid_normal_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_normal_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Invalid normal policy attached to user", + "name": "invalid_normal_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + } + }, + { + "address": "aws_iam_policy.invalid_user_policy_attachment_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_user_policy_attachment_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Invalid user policy attachment policy", + "name": "invalid_user_policy_attachment_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + } + }, + { + "address": "aws_iam_policy.valid_group_blank_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_blank_users_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Valid group blank users policy", + "name": "valid_group_blank_users_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + } + }, + { + "address": "aws_iam_policy.valid_group_empty_list_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_empty_list_users_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Valid group empty list users policy", + "name": "valid_group_empty_list_users_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + } + }, + { + "address": "aws_iam_policy.valid_group_missing_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_missing_users_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Valid group missing users policy", + "name": "valid_group_missing_users_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + } + }, + { + "address": "aws_iam_policy.valid_role_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_role_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": "Valid role policy", + "name": "valid_role_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + } + }, + { + "address": "aws_iam_policy_attachment.invalid_normal_policy_attachment", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "invalid_normal_policy_attachment", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "groups": null, + "name": "invalid_normal_policy_attachment", + "roles": null, + "users": [ + "invalid_normal_policy_user" + ] + } + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_blank_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_blank_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "groups": [ + "valid_group_blank_users" + ], + "name": "valid_group_policy_attachment_blank_users", + "roles": null, + "users": [ + "" + ] + } + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_empty_list_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_empty_list_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "groups": [ + "valid_group_empty_list_users" + ], + "name": "valid_group_policy_attachment_empty_list_users", + "roles": null, + "users": null + } + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_missing_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_missing_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "groups": [ + "valid_group_missing_users" + ], + "name": "valid_group_policy_attachment_missing_users", + "roles": null, + "users": null + } + }, + { + "address": "aws_iam_policy_attachment.valid_role_policy_attachment", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_role_policy_attachment", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "groups": null, + "name": "valid_role_policy_attachment", + "roles": [ + "valid_role" + ], + "users": null + } + }, + { + "address": "aws_iam_role.valid_role", + "mode": "managed", + "type": "aws_iam_role", + "name": "valid_role", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\n\"Version\": \"2012-10-17\",\n\"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "valid_role", + "name_prefix": null, + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user.invalid_normal_policy_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_normal_policy_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "force_destroy": false, + "name": "invalid_normal_policy_user", + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user.invalid_user_policy_attachment_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_user_policy_attachment_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "force_destroy": false, + "name": "invalid_user_policy_attachment_user", + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user.invalid_user_policy_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_user_policy_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "force_destroy": false, + "name": "invalid_user_policy_user", + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user.valid_group_blank_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_blank_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "force_destroy": false, + "name": "valid_group_blank_user", + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user.valid_group_empty_list_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_empty_list_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "force_destroy": false, + "name": "valid_group_empty_list_user", + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user.valid_group_missing_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_missing_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "force_destroy": false, + "name": "valid_group_missing_user", + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_user_policy.invalid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "invalid_user_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "invalid_user_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "user": "invalid_user_policy_user" + } + }, + { + "address": "aws_iam_user_policy_attachment.invalid_user_policy_attachment", + "mode": "managed", + "type": "aws_iam_user_policy_attachment", + "name": "invalid_user_policy_attachment", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "user": "invalid_user_policy_attachment_user" + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_group.valid_group_blank_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_blank_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "valid_group_blank_users", + "path": "/" + }, + "after_unknown": { + "arn": true, + "id": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_group.valid_group_empty_list_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_empty_list_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "valid_group_empty_list_users", + "path": "/" + }, + "after_unknown": { + "arn": true, + "id": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_group.valid_group_missing_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_missing_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "valid_group_missing_users", + "path": "/" + }, + "after_unknown": { + "arn": true, + "id": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_group_membership.valid_group_blank_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_blank_users_membership", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "group": "valid_group_blank_users", + "name": "valid_group_blank_users_membership", + "users": [ + "valid_group_blank_user" + ] + }, + "after_unknown": { + "id": true, + "users": [ + false + ] + } + } + }, + { + "address": "aws_iam_group_membership.valid_group_empty_list_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_empty_list_users_membership", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "group": "valid_group_empty_list_users", + "name": "valid_group_empty_list_users_membership", + "users": [ + "valid_group_empty_list_user" + ] + }, + "after_unknown": { + "id": true, + "users": [ + false + ] + } + } + }, + { + "address": "aws_iam_group_membership.valid_group_missing_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_missing_users_membership", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "group": "valid_group_missing_users", + "name": "valid_group_missing_users_membership", + "users": [ + "valid_group_missing_user" + ] + }, + "after_unknown": { + "id": true, + "users": [ + false + ] + } + } + }, + { + "address": "aws_iam_policy.invalid_normal_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_normal_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Invalid normal policy attached to user", + "name": "invalid_normal_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "policy_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_iam_policy.invalid_user_policy_attachment_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_user_policy_attachment_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Invalid user policy attachment policy", + "name": "invalid_user_policy_attachment_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "policy_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_iam_policy.valid_group_blank_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_blank_users_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Valid group blank users policy", + "name": "valid_group_blank_users_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "policy_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_iam_policy.valid_group_empty_list_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_empty_list_users_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Valid group empty list users policy", + "name": "valid_group_empty_list_users_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "policy_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_iam_policy.valid_group_missing_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_missing_users_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Valid group missing users policy", + "name": "valid_group_missing_users_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "policy_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_iam_policy.valid_role_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_role_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Valid role policy", + "name": "valid_role_policy", + "name_prefix": null, + "path": "/", + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "policy_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_iam_policy_attachment.invalid_normal_policy_attachment", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "invalid_normal_policy_attachment", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "groups": null, + "name": "invalid_normal_policy_attachment", + "roles": null, + "users": [ + "invalid_normal_policy_user" + ] + }, + "after_unknown": { + "id": true, + "policy_arn": true, + "users": [ + false + ] + } + } + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_blank_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_blank_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "groups": [ + "valid_group_blank_users" + ], + "name": "valid_group_policy_attachment_blank_users", + "roles": null, + "users": [ + "" + ] + }, + "after_unknown": { + "groups": [ + false + ], + "id": true, + "policy_arn": true, + "users": [ + false + ] + } + } + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_empty_list_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_empty_list_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "groups": [ + "valid_group_empty_list_users" + ], + "name": "valid_group_policy_attachment_empty_list_users", + "roles": null, + "users": null + }, + "after_unknown": { + "groups": [ + false + ], + "id": true, + "policy_arn": true + } + } + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_missing_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_missing_users", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "groups": [ + "valid_group_missing_users" + ], + "name": "valid_group_policy_attachment_missing_users", + "roles": null, + "users": null + }, + "after_unknown": { + "groups": [ + false + ], + "id": true, + "policy_arn": true + } + } + }, + { + "address": "aws_iam_policy_attachment.valid_role_policy_attachment", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_role_policy_attachment", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "groups": null, + "name": "valid_role_policy_attachment", + "roles": [ + "valid_role" + ], + "users": null + }, + "after_unknown": { + "id": true, + "policy_arn": true, + "roles": [ + false + ] + } + } + }, + { + "address": "aws_iam_role.valid_role", + "mode": "managed", + "type": "aws_iam_role", + "name": "valid_role", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\n\"Version\": \"2012-10-17\",\n\"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "valid_role", + "name_prefix": null, + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user.invalid_normal_policy_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_normal_policy_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "force_destroy": false, + "name": "invalid_normal_policy_user", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user.invalid_user_policy_attachment_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_user_policy_attachment_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "force_destroy": false, + "name": "invalid_user_policy_attachment_user", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user.invalid_user_policy_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_user_policy_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "force_destroy": false, + "name": "invalid_user_policy_user", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user.valid_group_blank_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_blank_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "force_destroy": false, + "name": "valid_group_blank_user", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user.valid_group_empty_list_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_empty_list_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "force_destroy": false, + "name": "valid_group_empty_list_user", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user.valid_group_missing_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_missing_user", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "force_destroy": false, + "name": "valid_group_missing_user", + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_user_policy.invalid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "invalid_user_policy", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "invalid_user_policy", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n", + "user": "invalid_user_policy_user" + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "aws_iam_user_policy_attachment.invalid_user_policy_attachment", + "mode": "managed", + "type": "aws_iam_user_policy_attachment", + "name": "invalid_user_policy_attachment", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "user": "invalid_user_policy_attachment_user" + }, + "after_unknown": { + "id": true, + "policy_arn": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_group.valid_group_blank_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_blank_users", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_group_blank_users" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_group.valid_group_empty_list_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_empty_list_users", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_group_empty_list_users" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_group.valid_group_missing_users", + "mode": "managed", + "type": "aws_iam_group", + "name": "valid_group_missing_users", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_group_missing_users" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_group_membership.valid_group_blank_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_blank_users_membership", + "provider_config_key": "aws", + "expressions": { + "group": { + "references": [ + "aws_iam_group.valid_group_blank_users" + ] + }, + "name": { + "constant_value": "valid_group_blank_users_membership" + }, + "users": { + "references": [ + "aws_iam_user.valid_group_blank_user" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_group_membership.valid_group_empty_list_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_empty_list_users_membership", + "provider_config_key": "aws", + "expressions": { + "group": { + "references": [ + "aws_iam_group.valid_group_empty_list_users" + ] + }, + "name": { + "constant_value": "valid_group_empty_list_users_membership" + }, + "users": { + "references": [ + "aws_iam_user.valid_group_empty_list_user" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_group_membership.valid_group_missing_users_membership", + "mode": "managed", + "type": "aws_iam_group_membership", + "name": "valid_group_missing_users_membership", + "provider_config_key": "aws", + "expressions": { + "group": { + "references": [ + "aws_iam_group.valid_group_missing_users" + ] + }, + "name": { + "constant_value": "valid_group_missing_users_membership" + }, + "users": { + "references": [ + "aws_iam_user.valid_group_missing_user" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.invalid_normal_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_normal_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Invalid normal policy attached to user" + }, + "name": { + "constant_value": "invalid_normal_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.invalid_user_policy_attachment_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "invalid_user_policy_attachment_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Invalid user policy attachment policy" + }, + "name": { + "constant_value": "invalid_user_policy_attachment_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.valid_group_blank_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_blank_users_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Valid group blank users policy" + }, + "name": { + "constant_value": "valid_group_blank_users_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.valid_group_empty_list_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_empty_list_users_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Valid group empty list users policy" + }, + "name": { + "constant_value": "valid_group_empty_list_users_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.valid_group_missing_users_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_group_missing_users_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Valid group missing users policy" + }, + "name": { + "constant_value": "valid_group_missing_users_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Deny\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy.valid_role_policy", + "mode": "managed", + "type": "aws_iam_policy", + "name": "valid_role_policy", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "Valid role policy" + }, + "name": { + "constant_value": "valid_role_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy_attachment.invalid_normal_policy_attachment", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "invalid_normal_policy_attachment", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_normal_policy_attachment" + }, + "policy_arn": { + "references": [ + "aws_iam_policy.invalid_normal_policy" + ] + }, + "users": { + "references": [ + "aws_iam_user.invalid_normal_policy_user" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_blank_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_blank_users", + "provider_config_key": "aws", + "expressions": { + "groups": { + "references": [ + "aws_iam_group.valid_group_blank_users" + ] + }, + "name": { + "constant_value": "valid_group_policy_attachment_blank_users" + }, + "policy_arn": { + "references": [ + "aws_iam_policy.valid_group_blank_users_policy" + ] + }, + "users": { + "constant_value": [ + "" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_empty_list_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_empty_list_users", + "provider_config_key": "aws", + "expressions": { + "groups": { + "references": [ + "aws_iam_group.valid_group_empty_list_users" + ] + }, + "name": { + "constant_value": "valid_group_policy_attachment_empty_list_users" + }, + "policy_arn": { + "references": [ + "aws_iam_policy.valid_group_empty_list_users_policy" + ] + }, + "users": { + "constant_value": [] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy_attachment.valid_group_policy_attachment_missing_users", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_group_policy_attachment_missing_users", + "provider_config_key": "aws", + "expressions": { + "groups": { + "references": [ + "aws_iam_group.valid_group_missing_users" + ] + }, + "name": { + "constant_value": "valid_group_policy_attachment_missing_users" + }, + "policy_arn": { + "references": [ + "aws_iam_policy.valid_group_missing_users_policy" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_policy_attachment.valid_role_policy_attachment", + "mode": "managed", + "type": "aws_iam_policy_attachment", + "name": "valid_role_policy_attachment", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_role_policy_attachment" + }, + "policy_arn": { + "references": [ + "aws_iam_policy.valid_role_policy" + ] + }, + "roles": { + "references": [ + "aws_iam_role.valid_role" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role.valid_role", + "mode": "managed", + "type": "aws_iam_role", + "name": "valid_role", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": { + "constant_value": "{\n\"Version\": \"2012-10-17\",\n\"Statement\": [\n {\n \"Action\": \"sts:AssumeRole\",\n \"Principal\": {\n \"Service\": \"ec2.amazonaws.com\"\n },\n \"Effect\": \"Allow\",\n \"Sid\": \"\"\n }\n ]\n}\n" + }, + "name": { + "constant_value": "valid_role" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user.invalid_normal_policy_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_normal_policy_user", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_normal_policy_user" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user.invalid_user_policy_attachment_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_user_policy_attachment_user", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_user_policy_attachment_user" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user.invalid_user_policy_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "invalid_user_policy_user", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_user_policy_user" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user.valid_group_blank_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_blank_user", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_group_blank_user" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user.valid_group_empty_list_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_empty_list_user", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_group_empty_list_user" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user.valid_group_missing_user", + "mode": "managed", + "type": "aws_iam_user", + "name": "valid_group_missing_user", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_group_missing_user" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user_policy.invalid_user_policy", + "mode": "managed", + "type": "aws_iam_user_policy", + "name": "invalid_user_policy", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_user_policy" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"ec2:Describe*\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "user": { + "references": [ + "aws_iam_user.invalid_user_policy_user" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_user_policy_attachment.invalid_user_policy_attachment", + "mode": "managed", + "type": "aws_iam_user_policy_attachment", + "name": "invalid_user_policy_attachment", + "provider_config_key": "aws", + "expressions": { + "policy_arn": { + "references": [ + "aws_iam_policy.invalid_user_policy_attachment_policy" + ] + }, + "user": { + "references": [ + "aws_iam_user.invalid_user_policy_attachment_user" + ] + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego index 452ae71a..fc25a2ee 100644 --- a/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego +++ b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,225 +16,17 @@ # # tests/rules/tf/aws/kms/inputs/key_rotation_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.kms.inputs.key_rotation_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_kms_key.blank-invalid", - "mode": "managed", - "type": "aws_kms_key", - "name": "blank-invalid", - "provider_name": "aws", - "schema_version": 0, - "values": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "description": "KMS key 3", - "enable_key_rotation": false, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - } - }, - { - "address": "aws_kms_key.invalid", - "mode": "managed", - "type": "aws_kms_key", - "name": "invalid", - "provider_name": "aws", - "schema_version": 0, - "values": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "description": "KMS key 2", - "enable_key_rotation": false, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - } - }, - { - "address": "aws_kms_key.valid", - "mode": "managed", - "type": "aws_kms_key", - "name": "valid", - "provider_name": "aws", - "schema_version": 0, - "values": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "description": "KMS key 1", - "enable_key_rotation": true, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_kms_key.blank-invalid", - "mode": "managed", - "type": "aws_kms_key", - "name": "blank-invalid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "description": "KMS key 3", - "enable_key_rotation": false, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "key_id": true, - "policy": true - } - } - }, - { - "address": "aws_kms_key.invalid", - "mode": "managed", - "type": "aws_kms_key", - "name": "invalid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "description": "KMS key 2", - "enable_key_rotation": false, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "key_id": true, - "policy": true - } - } - }, - { - "address": "aws_kms_key.valid", - "mode": "managed", - "type": "aws_kms_key", - "name": "valid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "description": "KMS key 1", - "enable_key_rotation": true, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true, - "key_id": true, - "policy": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "version_constraint": "~> 2.41", - "expressions": { - "region": { - "constant_value": "us-west-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_kms_key.blank-invalid", - "mode": "managed", - "type": "aws_kms_key", - "name": "blank-invalid", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "KMS key 3" - } - }, - "schema_version": 0 - }, - { - "address": "aws_kms_key.invalid", - "mode": "managed", - "type": "aws_kms_key", - "name": "invalid", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "KMS key 2" - }, - "enable_key_rotation": { - "constant_value": false - } - }, - "schema_version": 0 - }, - { - "address": "aws_kms_key.valid", - "mode": "managed", - "type": "aws_kms_key", - "name": "valid", - "provider_config_key": "aws", - "expressions": { - "description": { - "constant_value": "KMS key 1" - }, - "enable_key_rotation": { - "constant_value": true - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("key_rotation_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.tfplan b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.tfplan new file mode 100644 index 00000000..90f33c0d --- /dev/null +++ b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.tfplan @@ -0,0 +1,209 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_kms_key.blank-invalid", + "mode": "managed", + "type": "aws_kms_key", + "name": "blank-invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "description": "KMS key 3", + "enable_key_rotation": false, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + } + }, + { + "address": "aws_kms_key.invalid", + "mode": "managed", + "type": "aws_kms_key", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "description": "KMS key 2", + "enable_key_rotation": false, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + } + }, + { + "address": "aws_kms_key.valid", + "mode": "managed", + "type": "aws_kms_key", + "name": "valid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "description": "KMS key 1", + "enable_key_rotation": true, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_kms_key.blank-invalid", + "mode": "managed", + "type": "aws_kms_key", + "name": "blank-invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "description": "KMS key 3", + "enable_key_rotation": false, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "key_id": true, + "policy": true + } + } + }, + { + "address": "aws_kms_key.invalid", + "mode": "managed", + "type": "aws_kms_key", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "description": "KMS key 2", + "enable_key_rotation": false, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "key_id": true, + "policy": true + } + } + }, + { + "address": "aws_kms_key.valid", + "mode": "managed", + "type": "aws_kms_key", + "name": "valid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "description": "KMS key 1", + "enable_key_rotation": true, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "key_id": true, + "policy": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "version_constraint": "~> 2.41", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_kms_key.blank-invalid", + "mode": "managed", + "type": "aws_kms_key", + "name": "blank-invalid", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "KMS key 3" + } + }, + "schema_version": 0 + }, + { + "address": "aws_kms_key.invalid", + "mode": "managed", + "type": "aws_kms_key", + "name": "invalid", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "KMS key 2" + }, + "enable_key_rotation": { + "constant_value": false + } + }, + "schema_version": 0 + }, + { + "address": "aws_kms_key.valid", + "mode": "managed", + "type": "aws_kms_key", + "name": "valid", + "provider_config_key": "aws", + "expressions": { + "description": { + "constant_value": "KMS key 1" + }, + "enable_key_rotation": { + "constant_value": true + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego index 4e018e96..e871c057 100644 --- a/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego +++ b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,461 +16,17 @@ # # tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.s3.inputs.bucket_sse_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_kms_key.key", - "mode": "managed", - "type": "aws_kms_key", - "name": "key", - "provider_name": "aws", - "schema_version": 0, - "values": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "enable_key_rotation": false, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - } - }, - { - "address": "aws_s3_bucket.aes_encrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "aes_encrypted", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - { - "kms_master_key_id": null, - "sse_algorithm": "AES256" - } - ] - } - ] - } - ], - "tags": null, - "website": [] - } - }, - { - "address": "aws_s3_bucket.kms_encrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "kms_encrypted", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - { - "sse_algorithm": "aws:kms" - } - ] - } - ] - } - ], - "tags": null, - "website": [] - } - }, - { - "address": "aws_s3_bucket.unencrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "unencrypted", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_kms_key.key", - "mode": "managed", - "type": "aws_kms_key", - "name": "key", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "customer_master_key_spec": "SYMMETRIC_DEFAULT", - "deletion_window_in_days": null, - "enable_key_rotation": false, - "is_enabled": true, - "key_usage": "ENCRYPT_DECRYPT", - "tags": null - }, - "after_unknown": { - "arn": true, - "description": true, - "id": true, - "key_id": true, - "policy": true - } - } - }, - { - "address": "aws_s3_bucket.aes_encrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "aes_encrypted", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - { - "kms_master_key_id": null, - "sse_algorithm": "AES256" - } - ] - } - ] - } - ], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - {} - ] - } - ] - } - ], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - }, - { - "address": "aws_s3_bucket.kms_encrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "kms_encrypted", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - { - "sse_algorithm": "aws:kms" - } - ] - } - ] - } - ], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - { - "kms_master_key_id": true - } - ] - } - ] - } - ], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - }, - { - "address": "aws_s3_bucket.unencrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "unencrypted", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": true, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-1" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_kms_key.key", - "mode": "managed", - "type": "aws_kms_key", - "name": "key", - "provider_config_key": "aws", - "schema_version": 0 - }, - { - "address": "aws_s3_bucket.aes_encrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "aes_encrypted", - "provider_config_key": "aws", - "expressions": { - "force_destroy": { - "constant_value": true - }, - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - { - "sse_algorithm": { - "constant_value": "AES256" - } - } - ] - } - ] - } - ] - }, - "schema_version": 0 - }, - { - "address": "aws_s3_bucket.kms_encrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "kms_encrypted", - "provider_config_key": "aws", - "expressions": { - "force_destroy": { - "constant_value": true - }, - "server_side_encryption_configuration": [ - { - "rule": [ - { - "apply_server_side_encryption_by_default": [ - { - "kms_master_key_id": { - "references": [ - "aws_kms_key.key" - ] - }, - "sse_algorithm": { - "constant_value": "aws:kms" - } - } - ] - } - ] - } - ] - }, - "schema_version": 0 - }, - { - "address": "aws_s3_bucket.unencrypted", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "unencrypted", - "provider_config_key": "aws", - "expressions": { - "force_destroy": { - "constant_value": true - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("bucket_sse_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tfplan b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tfplan new file mode 100644 index 00000000..89bd4638 --- /dev/null +++ b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.tfplan @@ -0,0 +1,453 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_kms_key.key", + "mode": "managed", + "type": "aws_kms_key", + "name": "key", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "enable_key_rotation": false, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + } + }, + { + "address": "aws_s3_bucket.aes_encrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "aes_encrypted", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + { + "kms_master_key_id": null, + "sse_algorithm": "AES256" + } + ], + "bucket_key_enabled": null + } + ] + } + ], + "tags": null, + "website": [] + } + }, + { + "address": "aws_s3_bucket.kms_encrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "kms_encrypted", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + { + "sse_algorithm": "aws:kms" + } + ], + "bucket_key_enabled": null + } + ] + } + ], + "tags": null, + "website": [] + } + }, + { + "address": "aws_s3_bucket.unencrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "unencrypted", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_kms_key.key", + "mode": "managed", + "type": "aws_kms_key", + "name": "key", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "customer_master_key_spec": "SYMMETRIC_DEFAULT", + "deletion_window_in_days": null, + "enable_key_rotation": false, + "is_enabled": true, + "key_usage": "ENCRYPT_DECRYPT", + "tags": null + }, + "after_unknown": { + "arn": true, + "description": true, + "id": true, + "key_id": true, + "policy": true, + "tags_all": true + } + } + }, + { + "address": "aws_s3_bucket.aes_encrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "aes_encrypted", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + { + "kms_master_key_id": null, + "sse_algorithm": "AES256" + } + ], + "bucket_key_enabled": null + } + ] + } + ], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + {} + ] + } + ] + } + ], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + }, + { + "address": "aws_s3_bucket.kms_encrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "kms_encrypted", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + { + "sse_algorithm": "aws:kms" + } + ], + "bucket_key_enabled": null + } + ] + } + ], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + { + "kms_master_key_id": true + } + ] + } + ] + } + ], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + }, + { + "address": "aws_s3_bucket.unencrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "unencrypted", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": true, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_kms_key.key", + "mode": "managed", + "type": "aws_kms_key", + "name": "key", + "provider_config_key": "aws", + "schema_version": 0 + }, + { + "address": "aws_s3_bucket.aes_encrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "aes_encrypted", + "provider_config_key": "aws", + "expressions": { + "force_destroy": { + "constant_value": true + }, + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + { + "sse_algorithm": { + "constant_value": "AES256" + } + } + ] + } + ] + } + ] + }, + "schema_version": 0 + }, + { + "address": "aws_s3_bucket.kms_encrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "kms_encrypted", + "provider_config_key": "aws", + "expressions": { + "force_destroy": { + "constant_value": true + }, + "server_side_encryption_configuration": [ + { + "rule": [ + { + "apply_server_side_encryption_by_default": [ + { + "kms_master_key_id": { + "references": [ + "aws_kms_key.key" + ] + }, + "sse_algorithm": { + "constant_value": "aws:kms" + } + } + ] + } + ] + } + ] + }, + "schema_version": 0 + }, + { + "address": "aws_s3_bucket.unencrypted", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "unencrypted", + "provider_config_key": "aws", + "expressions": { + "force_destroy": { + "constant_value": true + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego index b4cb6e36..55b3290e 100644 --- a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,529 +16,17 @@ # # tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.security_group.inputs.ingress_anywhere_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_security_group.invalid_allow_all", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_allow_all", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 0, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 65535 - } - ], - "name": "invalid_allow_all", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.invalid_include_443", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_include_443", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 442, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 444 - } - ], - "name": "invalid_include_valid_443", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.invalid_include_80", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_include_80", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 79, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 81 - } - ], - "name": "invalid_include_valid_80", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.valid_exact_443", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_exact_443", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 443, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 443 - } - ], - "name": "valid_exact_443", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.valid_exact_80", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_exact_80", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 80, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 80 - } - ], - "name": "valid_exact_80", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_security_group.invalid_allow_all", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_allow_all", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 0, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 65535 - } - ], - "name": "invalid_allow_all", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.invalid_include_443", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_include_443", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 442, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 444 - } - ], - "name": "invalid_include_valid_443", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.invalid_include_80", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_include_80", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 79, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 81 - } - ], - "name": "invalid_include_valid_80", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.valid_exact_443", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_exact_443", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 443, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 443 - } - ], - "name": "valid_exact_443", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.valid_exact_80", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_exact_80", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 80, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 80 - } - ], - "name": "valid_exact_80", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-west-1" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_security_group.invalid_allow_all", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_allow_all", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_allow_all" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.invalid_include_443", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_include_443", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_include_valid_443" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.invalid_include_80", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_include_80", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_include_valid_80" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.valid_exact_443", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_exact_443", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_exact_443" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.valid_exact_80", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_exact_80", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_exact_80" - } - }, - "schema_version": 1 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("ingress_anywhere_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tfplan b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tfplan new file mode 100644 index 00000000..92ef7ac0 --- /dev/null +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.tfplan @@ -0,0 +1,513 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_allow_all", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_allow_all", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 0, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 65535 + } + ], + "name": "invalid_allow_all", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_include_443", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_include_443", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 442, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 444 + } + ], + "name": "invalid_include_valid_443", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_include_80", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_include_80", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 79, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 81 + } + ], + "name": "invalid_include_valid_80", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_exact_443", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_exact_443", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_exact_443", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_exact_80", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_exact_80", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 80, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 80 + } + ], + "name": "valid_exact_80", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_security_group.invalid_allow_all", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_allow_all", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 0, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 65535 + } + ], + "name": "invalid_allow_all", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.invalid_include_443", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_include_443", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 442, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 444 + } + ], + "name": "invalid_include_valid_443", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.invalid_include_80", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_include_80", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 79, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 81 + } + ], + "name": "invalid_include_valid_80", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_exact_443", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_exact_443", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_exact_443", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_exact_80", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_exact_80", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 80, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 80 + } + ], + "name": "valid_exact_80", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-west-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_allow_all", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_allow_all", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_allow_all" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_include_443", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_include_443", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_include_valid_443" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_include_80", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_include_80", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_include_valid_80" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_exact_443", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_exact_443", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_exact_443" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_exact_80", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_exact_80", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_exact_80" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego index 28ec962c..8acbf719 100644 --- a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,432 +16,17 @@ # # tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.security_group.inputs.ingress_anywhere_rdp_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_security_group.invalid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_1", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 3389, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 3389 - } - ], - "name": "invalid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.invalid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_2", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 3380, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 3390 - } - ], - "name": "invalid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.valid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_1", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 22, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 22 - } - ], - "name": "valid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.valid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_2", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "10.10.0.0/16" - ], - "description": "", - "from_port": 3389, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 3389 - } - ], - "name": "valid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_security_group.invalid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_1", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 3389, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 3389 - } - ], - "name": "invalid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.invalid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_2", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 3380, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 3390 - } - ], - "name": "invalid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.valid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_1", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 22, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 22 - } - ], - "name": "valid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.valid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_2", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "10.10.0.0/16" - ], - "description": "", - "from_port": 3389, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 3389 - } - ], - "name": "valid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-1" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_security_group.invalid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_1", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_sg_1" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.invalid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_2", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_sg_2" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.valid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_1", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_sg_1" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.valid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_2", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_sg_2" - } - }, - "schema_version": 1 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("ingress_anywhere_rdp_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tfplan b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tfplan new file mode 100644 index 00000000..6bdab634 --- /dev/null +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.tfplan @@ -0,0 +1,416 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 3389, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 3389 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 3380, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 3390 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 3389, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 3389 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 3389, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 3389 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 3380, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 3390 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 3389, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 3389 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_2" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_2" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego index 6ea7be3d..ce257259 100644 --- a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,432 +16,17 @@ # # tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.security_group.inputs.ingress_anywhere_ssh_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_security_group.invalid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_1", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 22, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 22 - } - ], - "name": "invalid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.invalid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_2", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 20, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 25 - } - ], - "name": "invalid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.valid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_1", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 443, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 443 - } - ], - "name": "valid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - }, - { - "address": "aws_security_group.valid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_2", - "provider_name": "aws", - "schema_version": 1, - "values": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "10.10.0.0/16" - ], - "description": "", - "from_port": 22, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 22 - } - ], - "name": "valid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_security_group.invalid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_1", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 22, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 22 - } - ], - "name": "invalid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.invalid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_2", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 20, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 25 - } - ], - "name": "invalid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.valid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_1", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "0.0.0.0/0" - ], - "description": "", - "from_port": 443, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 443 - } - ], - "name": "valid_sg_1", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - }, - { - "address": "aws_security_group.valid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_2", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": "Managed by Terraform", - "ingress": [ - { - "cidr_blocks": [ - "10.10.0.0/16" - ], - "description": "", - "from_port": 22, - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "protocol": "tcp", - "security_groups": [], - "self": false, - "to_port": 22 - } - ], - "name": "valid_sg_2", - "name_prefix": null, - "revoke_rules_on_delete": false, - "tags": null, - "timeouts": null - }, - "after_unknown": { - "arn": true, - "egress": true, - "id": true, - "ingress": [ - { - "cidr_blocks": [ - false - ], - "ipv6_cidr_blocks": [], - "prefix_list_ids": [], - "security_groups": [] - } - ], - "owner_id": true, - "vpc_id": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-1" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_security_group.invalid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_1", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_sg_1" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.invalid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "invalid_sg_2", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "invalid_sg_2" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.valid_sg_1", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_1", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_sg_1" - } - }, - "schema_version": 1 - }, - { - "address": "aws_security_group.valid_sg_2", - "mode": "managed", - "type": "aws_security_group", - "name": "valid_sg_2", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "valid_sg_2" - } - }, - "schema_version": 1 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("ingress_anywhere_ssh_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tfplan b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tfplan new file mode 100644 index 00000000..3a7759bb --- /dev/null +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.tfplan @@ -0,0 +1,416 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_2" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_2" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego index 7f08034b..784a5922 100644 --- a/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego +++ b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,417 +16,17 @@ # # tests/rules/tf/aws/vpc/inputs/flow_log_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.vpc.inputs.flow_log_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_cloudwatch_log_group.example", - "mode": "managed", - "type": "aws_cloudwatch_log_group", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "kms_key_id": null, - "name": "example", - "name_prefix": null, - "retention_in_days": 0, - "tags": null - } - }, - { - "address": "aws_flow_log.valid_vpc_flow_log", - "mode": "managed", - "type": "aws_flow_log", - "name": "valid_vpc_flow_log", - "provider_name": "aws", - "schema_version": 0, - "values": { - "eni_id": null, - "log_destination_type": "cloud-watch-logs", - "max_aggregation_interval": 600, - "subnet_id": null, - "tags": null, - "traffic_type": "ALL" - } - }, - { - "address": "aws_iam_role.example", - "mode": "managed", - "type": "aws_iam_role", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"vpc-flow-logs.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n", - "description": null, - "force_detach_policies": false, - "max_session_duration": 3600, - "name": "example", - "name_prefix": null, - "path": "/", - "permissions_boundary": null, - "tags": null - } - }, - { - "address": "aws_iam_role_policy.example", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "name": "example", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n \"logs:DescribeLogGroups\",\n \"logs:DescribeLogStreams\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - } - }, - { - "address": "aws_vpc.invalid_vpc", - "mode": "managed", - "type": "aws_vpc", - "name": "invalid_vpc", - "provider_name": "aws", - "schema_version": 1, - "values": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": null - } - }, - { - "address": "aws_vpc.valid_vpc", - "mode": "managed", - "type": "aws_vpc", - "name": "valid_vpc", - "provider_name": "aws", - "schema_version": 1, - "values": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_cloudwatch_log_group.example", - "mode": "managed", - "type": "aws_cloudwatch_log_group", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "kms_key_id": null, - "name": "example", - "name_prefix": null, - "retention_in_days": 0, - "tags": null - }, - "after_unknown": { - "arn": true, - "id": true - } - } - }, - { - "address": "aws_flow_log.valid_vpc_flow_log", - "mode": "managed", - "type": "aws_flow_log", - "name": "valid_vpc_flow_log", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "eni_id": null, - "log_destination_type": "cloud-watch-logs", - "max_aggregation_interval": 600, - "subnet_id": null, - "tags": null, - "traffic_type": "ALL" - }, - "after_unknown": { - "arn": true, - "iam_role_arn": true, - "id": true, - "log_destination": true, - "log_format": true, - "log_group_name": true, - "vpc_id": true - } - } - }, - { - "address": "aws_iam_role.example", - "mode": "managed", - "type": "aws_iam_role", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"vpc-flow-logs.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n", - "description": null, - "force_detach_policies": false, - "max_session_duration": 3600, - "name": "example", - "name_prefix": null, - "path": "/", - "permissions_boundary": null, - "tags": null - }, - "after_unknown": { - "arn": true, - "create_date": true, - "id": true, - "unique_id": true - } - } - }, - { - "address": "aws_iam_role_policy.example", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "name": "example", - "name_prefix": null, - "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n \"logs:DescribeLogGroups\",\n \"logs:DescribeLogStreams\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "after_unknown": { - "id": true, - "role": true - } - } - }, - { - "address": "aws_vpc.invalid_vpc", - "mode": "managed", - "type": "aws_vpc", - "name": "invalid_vpc", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": null - }, - "after_unknown": { - "arn": true, - "default_network_acl_id": true, - "default_route_table_id": true, - "default_security_group_id": true, - "dhcp_options_id": true, - "enable_classiclink": true, - "enable_classiclink_dns_support": true, - "enable_dns_hostnames": true, - "id": true, - "ipv6_association_id": true, - "ipv6_cidr_block": true, - "main_route_table_id": true, - "owner_id": true - } - } - }, - { - "address": "aws_vpc.valid_vpc", - "mode": "managed", - "type": "aws_vpc", - "name": "valid_vpc", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": null - }, - "after_unknown": { - "arn": true, - "default_network_acl_id": true, - "default_route_table_id": true, - "default_security_group_id": true, - "dhcp_options_id": true, - "enable_classiclink": true, - "enable_classiclink_dns_support": true, - "enable_dns_hostnames": true, - "id": true, - "ipv6_association_id": true, - "ipv6_cidr_block": true, - "main_route_table_id": true, - "owner_id": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-1" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_cloudwatch_log_group.example", - "mode": "managed", - "type": "aws_cloudwatch_log_group", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "example" - } - }, - "schema_version": 0 - }, - { - "address": "aws_flow_log.valid_vpc_flow_log", - "mode": "managed", - "type": "aws_flow_log", - "name": "valid_vpc_flow_log", - "provider_config_key": "aws", - "expressions": { - "iam_role_arn": { - "references": [ - "aws_iam_role.example" - ] - }, - "log_destination": { - "references": [ - "aws_cloudwatch_log_group.example" - ] - }, - "traffic_type": { - "constant_value": "ALL" - }, - "vpc_id": { - "references": [ - "aws_vpc.valid_vpc" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_role.example", - "mode": "managed", - "type": "aws_iam_role", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "assume_role_policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"vpc-flow-logs.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n" - }, - "name": { - "constant_value": "example" - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_role_policy.example", - "mode": "managed", - "type": "aws_iam_role_policy", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "name": { - "constant_value": "example" - }, - "policy": { - "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n \"logs:DescribeLogGroups\",\n \"logs:DescribeLogStreams\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" - }, - "role": { - "references": [ - "aws_iam_role.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_vpc.invalid_vpc", - "mode": "managed", - "type": "aws_vpc", - "name": "invalid_vpc", - "provider_config_key": "aws", - "expressions": { - "cidr_block": { - "constant_value": "10.0.0.0/16" - } - }, - "schema_version": 1 - }, - { - "address": "aws_vpc.valid_vpc", - "mode": "managed", - "type": "aws_vpc", - "name": "valid_vpc", - "provider_config_key": "aws", - "expressions": { - "cidr_block": { - "constant_value": "10.0.0.0/16" - } - }, - "schema_version": 1 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("flow_log_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.tfplan b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.tfplan new file mode 100644 index 00000000..76b600ed --- /dev/null +++ b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.tfplan @@ -0,0 +1,408 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_cloudwatch_log_group.example", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "kms_key_id": null, + "name": "example", + "name_prefix": null, + "retention_in_days": 0, + "tags": null + } + }, + { + "address": "aws_flow_log.valid_vpc_flow_log", + "mode": "managed", + "type": "aws_flow_log", + "name": "valid_vpc_flow_log", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "eni_id": null, + "log_destination_type": "cloud-watch-logs", + "max_aggregation_interval": 600, + "subnet_id": null, + "tags": null, + "traffic_type": "ALL" + } + }, + { + "address": "aws_iam_role.example", + "mode": "managed", + "type": "aws_iam_role", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"vpc-flow-logs.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "example", + "name_prefix": null, + "path": "/", + "permissions_boundary": null, + "tags": null + } + }, + { + "address": "aws_iam_role_policy.example", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "name": "example", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n \"logs:DescribeLogGroups\",\n \"logs:DescribeLogStreams\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + } + }, + { + "address": "aws_vpc.invalid_vpc", + "mode": "managed", + "type": "aws_vpc", + "name": "invalid_vpc", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": null + } + }, + { + "address": "aws_vpc.valid_vpc", + "mode": "managed", + "type": "aws_vpc", + "name": "valid_vpc", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_cloudwatch_log_group.example", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "kms_key_id": null, + "name": "example", + "name_prefix": null, + "retention_in_days": 0, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "tags_all": true + } + } + }, + { + "address": "aws_flow_log.valid_vpc_flow_log", + "mode": "managed", + "type": "aws_flow_log", + "name": "valid_vpc_flow_log", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "eni_id": null, + "log_destination_type": "cloud-watch-logs", + "max_aggregation_interval": 600, + "subnet_id": null, + "tags": null, + "traffic_type": "ALL" + }, + "after_unknown": { + "arn": true, + "iam_role_arn": true, + "id": true, + "log_destination": true, + "log_format": true, + "log_group_name": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_iam_role.example", + "mode": "managed", + "type": "aws_iam_role", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assume_role_policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"vpc-flow-logs.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n", + "description": null, + "force_detach_policies": false, + "max_session_duration": 3600, + "name": "example", + "name_prefix": null, + "path": "/", + "permissions_boundary": null, + "tags": null + }, + "after_unknown": { + "arn": true, + "create_date": true, + "id": true, + "inline_policy": true, + "managed_policy_arns": true, + "tags_all": true, + "unique_id": true + } + } + }, + { + "address": "aws_iam_role_policy.example", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "name": "example", + "name_prefix": null, + "policy": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n \"logs:DescribeLogGroups\",\n \"logs:DescribeLogStreams\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "after_unknown": { + "id": true, + "role": true + } + } + }, + { + "address": "aws_vpc.invalid_vpc", + "mode": "managed", + "type": "aws_vpc", + "name": "invalid_vpc", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": null + }, + "after_unknown": { + "arn": true, + "default_network_acl_id": true, + "default_route_table_id": true, + "default_security_group_id": true, + "dhcp_options_id": true, + "enable_classiclink": true, + "enable_classiclink_dns_support": true, + "enable_dns_hostnames": true, + "id": true, + "ipv6_association_id": true, + "ipv6_cidr_block": true, + "main_route_table_id": true, + "owner_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_vpc.valid_vpc", + "mode": "managed", + "type": "aws_vpc", + "name": "valid_vpc", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": null + }, + "after_unknown": { + "arn": true, + "default_network_acl_id": true, + "default_route_table_id": true, + "default_security_group_id": true, + "dhcp_options_id": true, + "enable_classiclink": true, + "enable_classiclink_dns_support": true, + "enable_dns_hostnames": true, + "id": true, + "ipv6_association_id": true, + "ipv6_cidr_block": true, + "main_route_table_id": true, + "owner_id": true, + "tags_all": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_cloudwatch_log_group.example", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "example" + } + }, + "schema_version": 0 + }, + { + "address": "aws_flow_log.valid_vpc_flow_log", + "mode": "managed", + "type": "aws_flow_log", + "name": "valid_vpc_flow_log", + "provider_config_key": "aws", + "expressions": { + "iam_role_arn": { + "references": [ + "aws_iam_role.example" + ] + }, + "log_destination": { + "references": [ + "aws_cloudwatch_log_group.example" + ] + }, + "traffic_type": { + "constant_value": "ALL" + }, + "vpc_id": { + "references": [ + "aws_vpc.valid_vpc" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role.example", + "mode": "managed", + "type": "aws_iam_role", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "assume_role_policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"\",\n \"Effect\": \"Allow\",\n \"Principal\": {\n \"Service\": \"vpc-flow-logs.amazonaws.com\"\n },\n \"Action\": \"sts:AssumeRole\"\n }\n ]\n}\n" + }, + "name": { + "constant_value": "example" + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_role_policy.example", + "mode": "managed", + "type": "aws_iam_role_policy", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "example" + }, + "policy": { + "constant_value": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Action\": [\n \"logs:CreateLogGroup\",\n \"logs:CreateLogStream\",\n \"logs:PutLogEvents\",\n \"logs:DescribeLogGroups\",\n \"logs:DescribeLogStreams\"\n ],\n \"Effect\": \"Allow\",\n \"Resource\": \"*\"\n }\n ]\n}\n" + }, + "role": { + "references": [ + "aws_iam_role.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_vpc.invalid_vpc", + "mode": "managed", + "type": "aws_vpc", + "name": "invalid_vpc", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "constant_value": "10.0.0.0/16" + } + }, + "schema_version": 1 + }, + { + "address": "aws_vpc.valid_vpc", + "mode": "managed", + "type": "aws_vpc", + "name": "valid_vpc", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "constant_value": "10.0.0.0/16" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego index 78cf172d..3dcb543e 100644 --- a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego +++ b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,1235 +16,17 @@ # # tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.network.inputs.security_group_no_inbound_22_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.13.4", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_network_security_group.invalidnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "invalidnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "235", - "30-50" - ], - "direction": "Inbound", - "name": "testrule2", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "*", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule1", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "*", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_group.invalidnsg2", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "invalidnsg2", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "22", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule3", - "priority": 103, - "protocol": "Tcp", - "source_address_prefix": "0.0.0.0/0", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_group.testnsg", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "testnsg", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "testnsg", - "resource_group_name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_group.validnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "validnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "validnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "20-50", - "235" - ], - "direction": "Inbound", - "name": "testrule5", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "20", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule4", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "Internet", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "20-25", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "invalidrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "22", - "27" - ], - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule3", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "0.0.0.0/0", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "18-30", - "88" - ], - "direction": "Inbound", - "name": "validrule3", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.validrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "34", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.validrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "22", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_network_security_group.invalidnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "invalidnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "235", - "30-50" - ], - "direction": "Inbound", - "name": "testrule2", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "*", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule1", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "*", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": [ - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [ - false, - false - ], - "source_address_prefixes": [ - false - ], - "source_application_security_group_ids": [], - "source_port_ranges": [] - }, - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [], - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_ranges": [] - } - ] - } - } - }, - { - "address": "azurerm_network_security_group.invalidnsg2", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "invalidnsg2", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "22", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule3", - "priority": 103, - "protocol": "Tcp", - "source_address_prefix": "0.0.0.0/0", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": [ - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [], - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_ranges": [] - } - ] - } - } - }, - { - "address": "azurerm_network_security_group.testnsg", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "testnsg", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "testnsg", - "resource_group_name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": true - } - } - }, - { - "address": "azurerm_network_security_group.validnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "validnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "validnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "20-50", - "235" - ], - "direction": "Inbound", - "name": "testrule5", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "20", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule4", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "Internet", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": [ - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [ - false, - false - ], - "source_address_prefixes": [ - false - ], - "source_application_security_group_ids": [], - "source_port_ranges": [] - }, - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [], - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_ranges": [] - } - ] - } - } - }, - { - "address": "azurerm_network_security_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "20-25", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "invalidrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "source_address_prefixes": [ - false, - false - ] - } - } - }, - { - "address": "azurerm_network_security_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "22", - "27" - ], - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "destination_port_ranges": [ - false, - false - ], - "id": true - } - } - }, - { - "address": "azurerm_network_security_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule3", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "0.0.0.0/0", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "18-30", - "88" - ], - "direction": "Inbound", - "name": "validrule3", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "destination_port_ranges": [ - false, - false - ], - "id": true - } - } - }, - { - "address": "azurerm_network_security_rule.validrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "34", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "source_address_prefixes": [ - false, - false - ] - } - } - }, - { - "address": "azurerm_network_security_rule.validrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "22", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "source_address_prefixes": [ - false - ] - } - } - }, - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_network_security_group.invalidnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg1", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidnsg1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_group.invalidnsg2", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg2", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidnsg2" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_group.testnsg", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "testnsg", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "testnsg" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_group.validnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "validnsg1", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "validnsg1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule1", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_range": { - "constant_value": "20-25" - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "invalidrule1" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefixes": { - "constant_value": [ - "10.10.10.10", - "*" - ] - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule2", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_ranges": { - "constant_value": [ - "22", - "27" - ] - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule2" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefix": { - "constant_value": "Any" - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule3", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "0.0.0.0/0" - }, - "destination_port_ranges": { - "constant_value": [ - "18-30", - "88" - ] - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule3" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefix": { - "constant_value": "Any" - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.validrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule1", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_range": { - "constant_value": "34" - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule1" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefixes": { - "constant_value": [ - "10.10.10.10", - "*" - ] - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.validrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule2", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_range": { - "constant_value": "22" - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule2" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefixes": { - "constant_value": [ - "10.10.10.10" - ] - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West US" - }, - "name": { - "constant_value": "acceptanceTestResourceGroup1" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("security_group_no_inbound_22_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tfplan b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tfplan new file mode 100644 index 00000000..a185498c --- /dev/null +++ b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.tfplan @@ -0,0 +1,1219 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_network_security_group.invalidnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "invalidnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "235", + "30-50" + ], + "direction": "Inbound", + "name": "testrule2", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "*", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule1", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "*", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_group.invalidnsg2", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "invalidnsg2", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "22", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule3", + "priority": 103, + "protocol": "Tcp", + "source_address_prefix": "0.0.0.0/0", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_group.testnsg", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "testnsg", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "testnsg", + "resource_group_name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_group.validnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "validnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "validnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "20-50", + "235" + ], + "direction": "Inbound", + "name": "testrule5", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "20", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule4", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "Internet", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "20-25", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "invalidrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "22", + "27" + ], + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule3", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "0.0.0.0/0", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "18-30", + "88" + ], + "direction": "Inbound", + "name": "validrule3", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.validrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "34", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.validrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "22", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_network_security_group.invalidnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "invalidnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "235", + "30-50" + ], + "direction": "Inbound", + "name": "testrule2", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "*", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule1", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "*", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": [ + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [ + false, + false + ], + "source_address_prefixes": [ + false + ], + "source_application_security_group_ids": [], + "source_port_ranges": [] + }, + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [], + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_ranges": [] + } + ] + } + } + }, + { + "address": "azurerm_network_security_group.invalidnsg2", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "invalidnsg2", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "22", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule3", + "priority": 103, + "protocol": "Tcp", + "source_address_prefix": "0.0.0.0/0", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": [ + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [], + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_ranges": [] + } + ] + } + } + }, + { + "address": "azurerm_network_security_group.testnsg", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "testnsg", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "testnsg", + "resource_group_name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": true + } + } + }, + { + "address": "azurerm_network_security_group.validnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "validnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "validnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "20-50", + "235" + ], + "direction": "Inbound", + "name": "testrule5", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "20", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule4", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "Internet", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": [ + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [ + false, + false + ], + "source_address_prefixes": [ + false + ], + "source_application_security_group_ids": [], + "source_port_ranges": [] + }, + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [], + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_ranges": [] + } + ] + } + } + }, + { + "address": "azurerm_network_security_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "20-25", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "invalidrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "source_address_prefixes": [ + false, + false + ] + } + } + }, + { + "address": "azurerm_network_security_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "22", + "27" + ], + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "destination_port_ranges": [ + false, + false + ], + "id": true + } + } + }, + { + "address": "azurerm_network_security_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule3", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "0.0.0.0/0", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "18-30", + "88" + ], + "direction": "Inbound", + "name": "validrule3", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "destination_port_ranges": [ + false, + false + ], + "id": true + } + } + }, + { + "address": "azurerm_network_security_rule.validrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "34", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "source_address_prefixes": [ + false, + false + ] + } + } + }, + { + "address": "azurerm_network_security_rule.validrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "22", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "source_address_prefixes": [ + false + ] + } + } + }, + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_network_security_group.invalidnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg1", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidnsg1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_group.invalidnsg2", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg2", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidnsg2" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_group.testnsg", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "testnsg", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "testnsg" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_group.validnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "validnsg1", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "validnsg1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule1", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_range": { + "constant_value": "20-25" + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "invalidrule1" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefixes": { + "constant_value": [ + "10.10.10.10", + "*" + ] + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule2", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_ranges": { + "constant_value": [ + "22", + "27" + ] + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule2" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefix": { + "constant_value": "Any" + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule3", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "0.0.0.0/0" + }, + "destination_port_ranges": { + "constant_value": [ + "18-30", + "88" + ] + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule3" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefix": { + "constant_value": "Any" + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.validrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule1", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_range": { + "constant_value": "34" + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule1" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefixes": { + "constant_value": [ + "10.10.10.10", + "*" + ] + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.validrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule2", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_range": { + "constant_value": "22" + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule2" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefixes": { + "constant_value": [ + "10.10.10.10" + ] + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West US" + }, + "name": { + "constant_value": "acceptanceTestResourceGroup1" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego index 2fdcd03b..2bcefba9 100644 --- a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego +++ b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,1235 +16,17 @@ # # tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.network.inputs.security_group_no_inbound_3389_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.13.4", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_network_security_group.invalidnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "invalidnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "235", - "30-50" - ], - "direction": "Inbound", - "name": "testrule2", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "*", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule1", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "*", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_group.invalidnsg2", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "invalidnsg2", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "3389", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule3", - "priority": 103, - "protocol": "Tcp", - "source_address_prefix": "0.0.0.0/0", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_group.testnsg", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "testnsg", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "testnsg", - "resource_group_name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_group.validnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "validnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "validnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "235", - "3380-3395" - ], - "direction": "Inbound", - "name": "testrule5", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "20", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule4", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "Internet", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "3350-3400", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "invalidrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "3389", - "3567" - ], - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule3", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "0.0.0.0/0", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "3350-3400", - "4300" - ], - "direction": "Inbound", - "name": "validrule3", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.validrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "34", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_network_security_rule.validrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "3389", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - } - }, - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_network_security_group.invalidnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "invalidnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "235", - "30-50" - ], - "direction": "Inbound", - "name": "testrule2", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "*", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule1", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "*", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": [ - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [ - false, - false - ], - "source_address_prefixes": [ - false - ], - "source_application_security_group_ids": [], - "source_port_ranges": [] - }, - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [], - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_ranges": [] - } - ] - } - } - }, - { - "address": "azurerm_network_security_group.invalidnsg2", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "invalidnsg2", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "3389", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule3", - "priority": 103, - "protocol": "Tcp", - "source_address_prefix": "0.0.0.0/0", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": [ - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [], - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_ranges": [] - } - ] - } - } - }, - { - "address": "azurerm_network_security_group.testnsg", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "testnsg", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "testnsg", - "resource_group_name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": true - } - } - }, - { - "address": "azurerm_network_security_group.validnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "validnsg1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "validnsg1", - "resource_group_name": "acceptanceTestResourceGroup1", - "security_rule": [ - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "", - "destination_port_ranges": [ - "235", - "3380-3395" - ], - "direction": "Inbound", - "name": "testrule5", - "priority": 101, - "protocol": "Tcp", - "source_address_prefix": "", - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - }, - { - "access": "Allow", - "description": "", - "destination_address_prefix": "*", - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_range": "20", - "destination_port_ranges": [], - "direction": "Inbound", - "name": "testrule4", - "priority": 100, - "protocol": "Tcp", - "source_address_prefix": "Internet", - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_range": "*", - "source_port_ranges": [] - } - ], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "security_rule": [ - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [ - false, - false - ], - "source_address_prefixes": [ - false - ], - "source_application_security_group_ids": [], - "source_port_ranges": [] - }, - { - "destination_address_prefixes": [], - "destination_application_security_group_ids": [], - "destination_port_ranges": [], - "source_address_prefixes": [], - "source_application_security_group_ids": [], - "source_port_ranges": [] - } - ] - } - } - }, - { - "address": "azurerm_network_security_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "3350-3400", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "invalidrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "source_address_prefixes": [ - false, - false - ] - } - } - }, - { - "address": "azurerm_network_security_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "3389", - "3567" - ], - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "destination_port_ranges": [ - false, - false - ], - "id": true - } - } - }, - { - "address": "azurerm_network_security_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule3", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "0.0.0.0/0", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": null, - "destination_port_ranges": [ - "3350-3400", - "4300" - ], - "direction": "Inbound", - "name": "validrule3", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": "Any", - "source_address_prefixes": null, - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "destination_port_ranges": [ - false, - false - ], - "id": true - } - } - }, - { - "address": "azurerm_network_security_rule.validrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule1", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "34", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule1", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "*", - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "source_address_prefixes": [ - false, - false - ] - } - } - }, - { - "address": "azurerm_network_security_rule.validrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule2", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "access": "Allow", - "description": null, - "destination_address_prefix": "*", - "destination_address_prefixes": null, - "destination_application_security_group_ids": null, - "destination_port_range": "3389", - "destination_port_ranges": null, - "direction": "Inbound", - "name": "validrule2", - "network_security_group_name": "testnsg", - "priority": 100, - "protocol": "Tcp", - "resource_group_name": "acceptanceTestResourceGroup1", - "source_address_prefix": null, - "source_address_prefixes": [ - "10.10.10.10" - ], - "source_application_security_group_ids": null, - "source_port_range": "*", - "source_port_ranges": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "source_address_prefixes": [ - false - ] - } - } - }, - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "registry.terraform.io/hashicorp/azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_network_security_group.invalidnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg1", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidnsg1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_group.invalidnsg2", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "invalidnsg2", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidnsg2" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_group.testnsg", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "testnsg", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "testnsg" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_group.validnsg1", - "mode": "managed", - "type": "azurerm_network_security_group", - "name": "validnsg1", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "validnsg1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule1", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_range": { - "constant_value": "3350-3400" - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "invalidrule1" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefixes": { - "constant_value": [ - "10.10.10.10", - "*" - ] - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule2", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_ranges": { - "constant_value": [ - "3389", - "3567" - ] - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule2" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefix": { - "constant_value": "Any" - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "invalidrule3", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "0.0.0.0/0" - }, - "destination_port_ranges": { - "constant_value": [ - "3350-3400", - "4300" - ] - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule3" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefix": { - "constant_value": "Any" - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.validrule1", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule1", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_range": { - "constant_value": "34" - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule1" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefixes": { - "constant_value": [ - "10.10.10.10", - "*" - ] - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_network_security_rule.validrule2", - "mode": "managed", - "type": "azurerm_network_security_rule", - "name": "validrule2", - "provider_config_key": "azurerm", - "expressions": { - "access": { - "constant_value": "Allow" - }, - "destination_address_prefix": { - "constant_value": "*" - }, - "destination_port_range": { - "constant_value": "3389" - }, - "direction": { - "constant_value": "Inbound" - }, - "name": { - "constant_value": "validrule2" - }, - "network_security_group_name": { - "references": [ - "azurerm_network_security_group.testnsg" - ] - }, - "priority": { - "constant_value": 100 - }, - "protocol": { - "constant_value": "Tcp" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "source_address_prefixes": { - "constant_value": [ - "10.10.10.10" - ] - }, - "source_port_range": { - "constant_value": "*" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West US" - }, - "name": { - "constant_value": "acceptanceTestResourceGroup1" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("security_group_no_inbound_3389_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tfplan b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tfplan new file mode 100644 index 00000000..8faaa3d3 --- /dev/null +++ b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.tfplan @@ -0,0 +1,1219 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_network_security_group.invalidnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "invalidnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "235", + "30-50" + ], + "direction": "Inbound", + "name": "testrule2", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "*", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule1", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "*", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_group.invalidnsg2", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "invalidnsg2", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "3389", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule3", + "priority": 103, + "protocol": "Tcp", + "source_address_prefix": "0.0.0.0/0", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_group.testnsg", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "testnsg", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "testnsg", + "resource_group_name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_group.validnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "validnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "validnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "235", + "3380-3395" + ], + "direction": "Inbound", + "name": "testrule5", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "20", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule4", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "Internet", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "3350-3400", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "invalidrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "3389", + "3567" + ], + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule3", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "0.0.0.0/0", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "3350-3400", + "4300" + ], + "direction": "Inbound", + "name": "validrule3", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.validrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "34", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_network_security_rule.validrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "3389", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + } + }, + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_network_security_group.invalidnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "invalidnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "235", + "30-50" + ], + "direction": "Inbound", + "name": "testrule2", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "*", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule1", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "*", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": [ + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [ + false, + false + ], + "source_address_prefixes": [ + false + ], + "source_application_security_group_ids": [], + "source_port_ranges": [] + }, + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [], + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_ranges": [] + } + ] + } + } + }, + { + "address": "azurerm_network_security_group.invalidnsg2", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "invalidnsg2", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "3389", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule3", + "priority": 103, + "protocol": "Tcp", + "source_address_prefix": "0.0.0.0/0", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": [ + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [], + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_ranges": [] + } + ] + } + } + }, + { + "address": "azurerm_network_security_group.testnsg", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "testnsg", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "testnsg", + "resource_group_name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": true + } + } + }, + { + "address": "azurerm_network_security_group.validnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "validnsg1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "validnsg1", + "resource_group_name": "acceptanceTestResourceGroup1", + "security_rule": [ + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "", + "destination_port_ranges": [ + "235", + "3380-3395" + ], + "direction": "Inbound", + "name": "testrule5", + "priority": 101, + "protocol": "Tcp", + "source_address_prefix": "", + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + }, + { + "access": "Allow", + "description": "", + "destination_address_prefix": "*", + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_range": "20", + "destination_port_ranges": [], + "direction": "Inbound", + "name": "testrule4", + "priority": 100, + "protocol": "Tcp", + "source_address_prefix": "Internet", + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_range": "*", + "source_port_ranges": [] + } + ], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "security_rule": [ + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [ + false, + false + ], + "source_address_prefixes": [ + false + ], + "source_application_security_group_ids": [], + "source_port_ranges": [] + }, + { + "destination_address_prefixes": [], + "destination_application_security_group_ids": [], + "destination_port_ranges": [], + "source_address_prefixes": [], + "source_application_security_group_ids": [], + "source_port_ranges": [] + } + ] + } + } + }, + { + "address": "azurerm_network_security_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "3350-3400", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "invalidrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "source_address_prefixes": [ + false, + false + ] + } + } + }, + { + "address": "azurerm_network_security_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "3389", + "3567" + ], + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "destination_port_ranges": [ + false, + false + ], + "id": true + } + } + }, + { + "address": "azurerm_network_security_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule3", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "0.0.0.0/0", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": null, + "destination_port_ranges": [ + "3350-3400", + "4300" + ], + "direction": "Inbound", + "name": "validrule3", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": "Any", + "source_address_prefixes": null, + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "destination_port_ranges": [ + false, + false + ], + "id": true + } + } + }, + { + "address": "azurerm_network_security_rule.validrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "34", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule1", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "*", + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "source_address_prefixes": [ + false, + false + ] + } + } + }, + { + "address": "azurerm_network_security_rule.validrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "access": "Allow", + "description": null, + "destination_address_prefix": "*", + "destination_address_prefixes": null, + "destination_application_security_group_ids": null, + "destination_port_range": "3389", + "destination_port_ranges": null, + "direction": "Inbound", + "name": "validrule2", + "network_security_group_name": "testnsg", + "priority": 100, + "protocol": "Tcp", + "resource_group_name": "acceptanceTestResourceGroup1", + "source_address_prefix": null, + "source_address_prefixes": [ + "10.10.10.10" + ], + "source_application_security_group_ids": null, + "source_port_range": "*", + "source_port_ranges": null, + "timeouts": null + }, + "after_unknown": { + "id": true, + "source_address_prefixes": [ + false + ] + } + } + }, + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_network_security_group.invalidnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg1", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidnsg1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_group.invalidnsg2", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "invalidnsg2", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidnsg2" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_group.testnsg", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "testnsg", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "testnsg" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_group.validnsg1", + "mode": "managed", + "type": "azurerm_network_security_group", + "name": "validnsg1", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "validnsg1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule1", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_range": { + "constant_value": "3350-3400" + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "invalidrule1" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefixes": { + "constant_value": [ + "10.10.10.10", + "*" + ] + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule2", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_ranges": { + "constant_value": [ + "3389", + "3567" + ] + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule2" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefix": { + "constant_value": "Any" + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "invalidrule3", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "0.0.0.0/0" + }, + "destination_port_ranges": { + "constant_value": [ + "3350-3400", + "4300" + ] + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule3" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefix": { + "constant_value": "Any" + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.validrule1", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule1", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_range": { + "constant_value": "34" + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule1" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefixes": { + "constant_value": [ + "10.10.10.10", + "*" + ] + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_network_security_rule.validrule2", + "mode": "managed", + "type": "azurerm_network_security_rule", + "name": "validrule2", + "provider_config_key": "azurerm", + "expressions": { + "access": { + "constant_value": "Allow" + }, + "destination_address_prefix": { + "constant_value": "*" + }, + "destination_port_range": { + "constant_value": "3389" + }, + "direction": { + "constant_value": "Inbound" + }, + "name": { + "constant_value": "validrule2" + }, + "network_security_group_name": { + "references": [ + "azurerm_network_security_group.testnsg" + ] + }, + "priority": { + "constant_value": 100 + }, + "protocol": { + "constant_value": "Tcp" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "source_address_prefixes": { + "constant_value": [ + "10.10.10.10" + ] + }, + "source_port_range": { + "constant_value": "*" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West US" + }, + "name": { + "constant_value": "acceptanceTestResourceGroup1" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego index e7c75a2d..4329c673 100644 --- a/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego +++ b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,522 +16,17 @@ # # tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.sql.inputs.firewall_no_inbound_all_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "location": "westus", - "name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule1", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "end_ip_address": "10.0.17.62", - "name": "invalidrule1", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "0.0.0.0", - "timeouts": null - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule2", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "end_ip_address": "0.0.0.0", - "name": "invalidrule2", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "0.0.0.0", - "timeouts": null - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule3", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "end_ip_address": "255.255.255.255", - "name": "invalidrule3", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "0.0.0.0", - "timeouts": null - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule4", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule4", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "end_ip_address": "255.255.255.255", - "name": "invalidrule4", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "10.0.17.62", - "timeouts": null - } - }, - { - "address": "azurerm_sql_firewall_rule.validrule1", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "validrule1", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "end_ip_address": "10.0.17.62", - "name": "validrule1", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "10.0.17.62", - "timeouts": null - } - }, - { - "address": "azurerm_sql_server.example", - "mode": "managed", - "type": "azurerm_sql_server", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "administrator_login": "4dm1n157r470r", - "administrator_login_password": "4-v3ry-53cr37-p455w0rd", - "connection_policy": "Default", - "identity": [], - "location": "westus", - "name": "mysqlserver", - "resource_group_name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null, - "version": "12.0" - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westus", - "name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "end_ip_address": "10.0.17.62", - "name": "invalidrule1", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "0.0.0.0", - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule2", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "end_ip_address": "0.0.0.0", - "name": "invalidrule2", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "0.0.0.0", - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule3", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "end_ip_address": "255.255.255.255", - "name": "invalidrule3", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "0.0.0.0", - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule4", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule4", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "end_ip_address": "255.255.255.255", - "name": "invalidrule4", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "10.0.17.62", - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_sql_firewall_rule.validrule1", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "validrule1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "end_ip_address": "10.0.17.62", - "name": "validrule1", - "resource_group_name": "acceptanceTestResourceGroup1", - "server_name": "mysqlserver", - "start_ip_address": "10.0.17.62", - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_sql_server.example", - "mode": "managed", - "type": "azurerm_sql_server", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "administrator_login": "4dm1n157r470r", - "administrator_login_password": "4-v3ry-53cr37-p455w0rd", - "connection_policy": "Default", - "identity": [], - "location": "westus", - "name": "mysqlserver", - "resource_group_name": "acceptanceTestResourceGroup1", - "tags": null, - "timeouts": null, - "version": "12.0" - }, - "after_unknown": { - "extended_auditing_policy": true, - "fully_qualified_domain_name": true, - "id": true, - "identity": [] - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West US" - }, - "name": { - "constant_value": "acceptanceTestResourceGroup1" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule1", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule1", - "provider_config_key": "azurerm", - "expressions": { - "end_ip_address": { - "constant_value": "10.0.17.62" - }, - "name": { - "constant_value": "invalidrule1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "server_name": { - "references": [ - "azurerm_sql_server.example" - ] - }, - "start_ip_address": { - "constant_value": "0.0.0.0" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule2", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule2", - "provider_config_key": "azurerm", - "expressions": { - "end_ip_address": { - "constant_value": "0.0.0.0" - }, - "name": { - "constant_value": "invalidrule2" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "server_name": { - "references": [ - "azurerm_sql_server.example" - ] - }, - "start_ip_address": { - "constant_value": "0.0.0.0" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule3", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule3", - "provider_config_key": "azurerm", - "expressions": { - "end_ip_address": { - "constant_value": "255.255.255.255" - }, - "name": { - "constant_value": "invalidrule3" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "server_name": { - "references": [ - "azurerm_sql_server.example" - ] - }, - "start_ip_address": { - "constant_value": "0.0.0.0" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_sql_firewall_rule.invalidrule4", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "invalidrule4", - "provider_config_key": "azurerm", - "expressions": { - "end_ip_address": { - "constant_value": "255.255.255.255" - }, - "name": { - "constant_value": "invalidrule4" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "server_name": { - "references": [ - "azurerm_sql_server.example" - ] - }, - "start_ip_address": { - "constant_value": "10.0.17.62" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_sql_firewall_rule.validrule1", - "mode": "managed", - "type": "azurerm_sql_firewall_rule", - "name": "validrule1", - "provider_config_key": "azurerm", - "expressions": { - "end_ip_address": { - "constant_value": "10.0.17.62" - }, - "name": { - "constant_value": "validrule1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "server_name": { - "references": [ - "azurerm_sql_server.example" - ] - }, - "start_ip_address": { - "constant_value": "10.0.17.62" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_sql_server.example", - "mode": "managed", - "type": "azurerm_sql_server", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "administrator_login": { - "constant_value": "4dm1n157r470r" - }, - "administrator_login_password": { - "constant_value": "4-v3ry-53cr37-p455w0rd" - }, - "location": { - "constant_value": "West US" - }, - "name": { - "constant_value": "mysqlserver" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "version": { - "constant_value": "12.0" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("firewall_no_inbound_all_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tfplan b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tfplan new file mode 100644 index 00000000..83a26630 --- /dev/null +++ b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.tfplan @@ -0,0 +1,506 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westus", + "name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "end_ip_address": "10.0.17.62", + "name": "invalidrule1", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "0.0.0.0", + "timeouts": null + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "end_ip_address": "0.0.0.0", + "name": "invalidrule2", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "0.0.0.0", + "timeouts": null + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule3", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "end_ip_address": "255.255.255.255", + "name": "invalidrule3", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "0.0.0.0", + "timeouts": null + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule4", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule4", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "end_ip_address": "255.255.255.255", + "name": "invalidrule4", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "10.0.17.62", + "timeouts": null + } + }, + { + "address": "azurerm_sql_firewall_rule.validrule1", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "validrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "end_ip_address": "10.0.17.62", + "name": "validrule1", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "10.0.17.62", + "timeouts": null + } + }, + { + "address": "azurerm_sql_server.example", + "mode": "managed", + "type": "azurerm_sql_server", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "administrator_login": "4dm1n157r470r", + "administrator_login_password": "4-v3ry-53cr37-p455w0rd", + "connection_policy": "Default", + "identity": [], + "location": "westus", + "name": "mysqlserver", + "resource_group_name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null, + "version": "12.0" + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westus", + "name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "end_ip_address": "10.0.17.62", + "name": "invalidrule1", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "0.0.0.0", + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "end_ip_address": "0.0.0.0", + "name": "invalidrule2", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "0.0.0.0", + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule3", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "end_ip_address": "255.255.255.255", + "name": "invalidrule3", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "0.0.0.0", + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule4", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule4", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "end_ip_address": "255.255.255.255", + "name": "invalidrule4", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "10.0.17.62", + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_sql_firewall_rule.validrule1", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "validrule1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "end_ip_address": "10.0.17.62", + "name": "validrule1", + "resource_group_name": "acceptanceTestResourceGroup1", + "server_name": "mysqlserver", + "start_ip_address": "10.0.17.62", + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_sql_server.example", + "mode": "managed", + "type": "azurerm_sql_server", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "administrator_login": "4dm1n157r470r", + "administrator_login_password": "4-v3ry-53cr37-p455w0rd", + "connection_policy": "Default", + "identity": [], + "location": "westus", + "name": "mysqlserver", + "resource_group_name": "acceptanceTestResourceGroup1", + "tags": null, + "timeouts": null, + "version": "12.0" + }, + "after_unknown": { + "extended_auditing_policy": true, + "fully_qualified_domain_name": true, + "id": true, + "identity": [] + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West US" + }, + "name": { + "constant_value": "acceptanceTestResourceGroup1" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule1", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule1", + "provider_config_key": "azurerm", + "expressions": { + "end_ip_address": { + "constant_value": "10.0.17.62" + }, + "name": { + "constant_value": "invalidrule1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "server_name": { + "references": [ + "azurerm_sql_server.example" + ] + }, + "start_ip_address": { + "constant_value": "0.0.0.0" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule2", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule2", + "provider_config_key": "azurerm", + "expressions": { + "end_ip_address": { + "constant_value": "0.0.0.0" + }, + "name": { + "constant_value": "invalidrule2" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "server_name": { + "references": [ + "azurerm_sql_server.example" + ] + }, + "start_ip_address": { + "constant_value": "0.0.0.0" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule3", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule3", + "provider_config_key": "azurerm", + "expressions": { + "end_ip_address": { + "constant_value": "255.255.255.255" + }, + "name": { + "constant_value": "invalidrule3" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "server_name": { + "references": [ + "azurerm_sql_server.example" + ] + }, + "start_ip_address": { + "constant_value": "0.0.0.0" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_sql_firewall_rule.invalidrule4", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "invalidrule4", + "provider_config_key": "azurerm", + "expressions": { + "end_ip_address": { + "constant_value": "255.255.255.255" + }, + "name": { + "constant_value": "invalidrule4" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "server_name": { + "references": [ + "azurerm_sql_server.example" + ] + }, + "start_ip_address": { + "constant_value": "10.0.17.62" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_sql_firewall_rule.validrule1", + "mode": "managed", + "type": "azurerm_sql_firewall_rule", + "name": "validrule1", + "provider_config_key": "azurerm", + "expressions": { + "end_ip_address": { + "constant_value": "10.0.17.62" + }, + "name": { + "constant_value": "validrule1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "server_name": { + "references": [ + "azurerm_sql_server.example" + ] + }, + "start_ip_address": { + "constant_value": "10.0.17.62" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_sql_server.example", + "mode": "managed", + "type": "azurerm_sql_server", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "administrator_login": { + "constant_value": "4dm1n157r470r" + }, + "administrator_login_password": { + "constant_value": "4-v3ry-53cr37-p455w0rd" + }, + "location": { + "constant_value": "West US" + }, + "name": { + "constant_value": "mysqlserver" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "version": { + "constant_value": "12.0" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego index 4965f333..d7844a2c 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,855 +16,17 @@ # # tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.account_deny_access_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount1", - "network_rules": [ - { - "default_action": "Allow" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount2", - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount1", - "network_rules": [ - { - "default_action": "Deny", - "ip_rules": [ - "100.0.0.1" - ] - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.validstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount2", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount2", - "network_rules": [ - { - "default_action": "Deny" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_subnet.example", - "mode": "managed", - "type": "azurerm_subnet", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "address_prefix": "10.0.2.0/24", - "delegation": [], - "enforce_private_link_endpoint_network_policies": false, - "enforce_private_link_service_network_policies": false, - "name": "subnetname", - "resource_group_name": "example-resources", - "service_endpoints": [ - "Microsoft.Sql", - "Microsoft.Storage" - ], - "timeouts": null, - "virtual_network_name": "virtnetname" - } - }, - { - "address": "azurerm_virtual_network.example", - "mode": "managed", - "type": "azurerm_virtual_network", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "address_space": [ - "10.0.0.0/16" - ], - "ddos_protection_plan": [], - "dns_servers": null, - "location": "westeurope", - "name": "virtnetname", - "resource_group_name": "example-resources", - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount1", - "network_rules": [ - { - "default_action": "Allow" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": [ - { - "bypass": true, - "ip_rules": true, - "virtual_network_subnet_ids": true - } - ], - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount2", - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": true, - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount1", - "network_rules": [ - { - "default_action": "Deny", - "ip_rules": [ - "100.0.0.1" - ] - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": [ - { - "bypass": true, - "ip_rules": [ - false - ], - "virtual_network_subnet_ids": true - } - ], - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_storage_account.validstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount2", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount2", - "network_rules": [ - { - "default_action": "Deny" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": [ - { - "bypass": true, - "ip_rules": true, - "virtual_network_subnet_ids": true - } - ], - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_subnet.example", - "mode": "managed", - "type": "azurerm_subnet", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "address_prefix": "10.0.2.0/24", - "delegation": [], - "enforce_private_link_endpoint_network_policies": false, - "enforce_private_link_service_network_policies": false, - "name": "subnetname", - "resource_group_name": "example-resources", - "service_endpoints": [ - "Microsoft.Sql", - "Microsoft.Storage" - ], - "timeouts": null, - "virtual_network_name": "virtnetname" - }, - "after_unknown": { - "address_prefixes": true, - "delegation": [], - "id": true, - "service_endpoints": [ - false, - false - ] - } - } - }, - { - "address": "azurerm_virtual_network.example", - "mode": "managed", - "type": "azurerm_virtual_network", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "address_space": [ - "10.0.0.0/16" - ], - "ddos_protection_plan": [], - "dns_servers": null, - "location": "westeurope", - "name": "virtnetname", - "resource_group_name": "example-resources", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "address_space": [ - false - ], - "ddos_protection_plan": [], - "guid": true, - "id": true, - "subnet": true - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West Europe" - }, - "name": { - "constant_value": "example-resources" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidstorageaccount1" - }, - "network_rules": [ - { - "default_action": { - "constant_value": "Allow" - } - } - ], - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidstorageaccount2" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "validstorageaccount1" - }, - "network_rules": [ - { - "default_action": { - "constant_value": "Deny" - }, - "ip_rules": { - "constant_value": [ - "100.0.0.1" - ] - }, - "virtual_network_subnet_ids": { - "references": [ - "azurerm_subnet.example" - ] - } - } - ], - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.validstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount2", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "validstorageaccount2" - }, - "network_rules": [ - { - "default_action": { - "constant_value": "Deny" - } - } - ], - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_subnet.example", - "mode": "managed", - "type": "azurerm_subnet", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "address_prefix": { - "constant_value": "10.0.2.0/24" - }, - "name": { - "constant_value": "subnetname" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "service_endpoints": { - "constant_value": [ - "Microsoft.Sql", - "Microsoft.Storage" - ] - }, - "virtual_network_name": { - "references": [ - "azurerm_virtual_network.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_virtual_network.example", - "mode": "managed", - "type": "azurerm_virtual_network", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "address_space": { - "constant_value": [ - "10.0.0.0/16" - ] - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "virtnetname" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("account_deny_access_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tfplan b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tfplan new file mode 100644 index 00000000..27717ba4 --- /dev/null +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.tfplan @@ -0,0 +1,853 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount1", + "network_rules": [ + { + "default_action": "Allow" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount2", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount1", + "network_rules": [ + { + "default_action": "Deny", + "ip_rules": [ + "100.0.0.1" + ] + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.validstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount2", + "network_rules": [ + { + "default_action": "Deny" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_subnet.example", + "mode": "managed", + "type": "azurerm_subnet", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "address_prefix": "10.0.2.0/24", + "delegation": [], + "enforce_private_link_endpoint_network_policies": false, + "enforce_private_link_service_network_policies": false, + "name": "subnetname", + "resource_group_name": "example-resources", + "service_endpoint_policy_ids": null, + "service_endpoints": [ + "Microsoft.Sql", + "Microsoft.Storage" + ], + "timeouts": null, + "virtual_network_name": "virtnetname" + } + }, + { + "address": "azurerm_virtual_network.example", + "mode": "managed", + "type": "azurerm_virtual_network", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "address_space": [ + "10.0.0.0/16" + ], + "bgp_community": null, + "ddos_protection_plan": [], + "dns_servers": null, + "location": "westeurope", + "name": "virtnetname", + "resource_group_name": "example-resources", + "tags": null, + "timeouts": null, + "vm_protection_enabled": false + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount1", + "network_rules": [ + { + "default_action": "Allow" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": [ + { + "bypass": true, + "ip_rules": true, + "virtual_network_subnet_ids": true + } + ], + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount2", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": true, + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount1", + "network_rules": [ + { + "default_action": "Deny", + "ip_rules": [ + "100.0.0.1" + ] + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": [ + { + "bypass": true, + "ip_rules": [ + false + ], + "virtual_network_subnet_ids": true + } + ], + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_storage_account.validstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount2", + "network_rules": [ + { + "default_action": "Deny" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": [ + { + "bypass": true, + "ip_rules": true, + "virtual_network_subnet_ids": true + } + ], + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_subnet.example", + "mode": "managed", + "type": "azurerm_subnet", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "address_prefix": "10.0.2.0/24", + "delegation": [], + "enforce_private_link_endpoint_network_policies": false, + "enforce_private_link_service_network_policies": false, + "name": "subnetname", + "resource_group_name": "example-resources", + "service_endpoint_policy_ids": null, + "service_endpoints": [ + "Microsoft.Sql", + "Microsoft.Storage" + ], + "timeouts": null, + "virtual_network_name": "virtnetname" + }, + "after_unknown": { + "address_prefixes": true, + "delegation": [], + "id": true, + "service_endpoints": [ + false, + false + ] + } + } + }, + { + "address": "azurerm_virtual_network.example", + "mode": "managed", + "type": "azurerm_virtual_network", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "address_space": [ + "10.0.0.0/16" + ], + "bgp_community": null, + "ddos_protection_plan": [], + "dns_servers": null, + "location": "westeurope", + "name": "virtnetname", + "resource_group_name": "example-resources", + "tags": null, + "timeouts": null, + "vm_protection_enabled": false + }, + "after_unknown": { + "address_space": [ + false + ], + "ddos_protection_plan": [], + "guid": true, + "id": true, + "subnet": true + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West Europe" + }, + "name": { + "constant_value": "example-resources" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidstorageaccount1" + }, + "network_rules": [ + { + "default_action": { + "constant_value": "Allow" + } + } + ], + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidstorageaccount2" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "validstorageaccount1" + }, + "network_rules": [ + { + "default_action": { + "constant_value": "Deny" + }, + "ip_rules": { + "constant_value": [ + "100.0.0.1" + ] + }, + "virtual_network_subnet_ids": { + "references": [ + "azurerm_subnet.example" + ] + } + } + ], + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.validstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount2", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "validstorageaccount2" + }, + "network_rules": [ + { + "default_action": { + "constant_value": "Deny" + } + } + ], + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_subnet.example", + "mode": "managed", + "type": "azurerm_subnet", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "address_prefix": { + "constant_value": "10.0.2.0/24" + }, + "name": { + "constant_value": "subnetname" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "service_endpoints": { + "constant_value": [ + "Microsoft.Sql", + "Microsoft.Storage" + ] + }, + "virtual_network_name": { + "references": [ + "azurerm_virtual_network.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_virtual_network.example", + "mode": "managed", + "type": "azurerm_virtual_network", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "address_space": { + "constant_value": [ + "10.0.0.0/16" + ] + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "virtnetname" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego index f87b0acc..73cd5cbd 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,935 +16,17 @@ # # tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.account_microsoft_services_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount1", - "network_rules": [ - { - "default_action": "Allow" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount2", - "network_rules": [ - { - "bypass": [ - "Logging", - "Metrics" - ], - "default_action": "Deny" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount1", - "network_rules": [ - { - "bypass": [ - "AzureServices" - ], - "default_action": "Deny", - "ip_rules": [ - "100.0.0.1" - ] - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.validstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount2", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount2", - "network_rules": [ - { - "bypass": [ - "AzureServices", - "Logging", - "Metrics" - ], - "default_action": "Deny" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_subnet.example", - "mode": "managed", - "type": "azurerm_subnet", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "address_prefix": "10.0.2.0/24", - "delegation": [], - "enforce_private_link_endpoint_network_policies": false, - "enforce_private_link_service_network_policies": false, - "name": "subnetname", - "resource_group_name": "example-resources", - "service_endpoints": [ - "Microsoft.Sql", - "Microsoft.Storage" - ], - "timeouts": null, - "virtual_network_name": "virtnetname" - } - }, - { - "address": "azurerm_virtual_network.example", - "mode": "managed", - "type": "azurerm_virtual_network", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "address_space": [ - "10.0.0.0/16" - ], - "ddos_protection_plan": [], - "dns_servers": null, - "location": "westeurope", - "name": "virtnetname", - "resource_group_name": "example-resources", - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount1", - "network_rules": [ - { - "default_action": "Allow" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": [ - { - "bypass": true, - "ip_rules": true, - "virtual_network_subnet_ids": true - } - ], - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount2", - "network_rules": [ - { - "bypass": [ - "Logging", - "Metrics" - ], - "default_action": "Deny" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": [ - { - "bypass": [ - false, - false - ], - "ip_rules": true, - "virtual_network_subnet_ids": true - } - ], - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount1", - "network_rules": [ - { - "bypass": [ - "AzureServices" - ], - "default_action": "Deny", - "ip_rules": [ - "100.0.0.1" - ] - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": [ - { - "bypass": [ - false - ], - "ip_rules": [ - false - ], - "virtual_network_subnet_ids": true - } - ], - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_storage_account.validstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount2", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount2", - "network_rules": [ - { - "bypass": [ - "AzureServices", - "Logging", - "Metrics" - ], - "default_action": "Deny" - } - ], - "resource_group_name": "example-resources", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": [ - { - "bypass": [ - false, - false, - false - ], - "ip_rules": true, - "virtual_network_subnet_ids": true - } - ], - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - }, - { - "address": "azurerm_subnet.example", - "mode": "managed", - "type": "azurerm_subnet", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "address_prefix": "10.0.2.0/24", - "delegation": [], - "enforce_private_link_endpoint_network_policies": false, - "enforce_private_link_service_network_policies": false, - "name": "subnetname", - "resource_group_name": "example-resources", - "service_endpoints": [ - "Microsoft.Sql", - "Microsoft.Storage" - ], - "timeouts": null, - "virtual_network_name": "virtnetname" - }, - "after_unknown": { - "address_prefixes": true, - "delegation": [], - "id": true, - "service_endpoints": [ - false, - false - ] - } - } - }, - { - "address": "azurerm_virtual_network.example", - "mode": "managed", - "type": "azurerm_virtual_network", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "address_space": [ - "10.0.0.0/16" - ], - "ddos_protection_plan": [], - "dns_servers": null, - "location": "westeurope", - "name": "virtnetname", - "resource_group_name": "example-resources", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "address_space": [ - false - ], - "ddos_protection_plan": [], - "guid": true, - "id": true, - "subnet": true - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West Europe" - }, - "name": { - "constant_value": "example-resources" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidstorageaccount1" - }, - "network_rules": [ - { - "default_action": { - "constant_value": "Allow" - } - } - ], - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidstorageaccount2" - }, - "network_rules": [ - { - "bypass": { - "constant_value": [ - "Logging", - "Metrics" - ] - }, - "default_action": { - "constant_value": "Deny" - } - } - ], - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "enable_https_traffic_only": { - "constant_value": true - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "validstorageaccount1" - }, - "network_rules": [ - { - "bypass": { - "constant_value": [ - "AzureServices" - ] - }, - "default_action": { - "constant_value": "Deny" - }, - "ip_rules": { - "constant_value": [ - "100.0.0.1" - ] - }, - "virtual_network_subnet_ids": { - "references": [ - "azurerm_subnet.example" - ] - } - } - ], - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.validstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount2", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "enable_https_traffic_only": { - "constant_value": true - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "validstorageaccount2" - }, - "network_rules": [ - { - "bypass": { - "constant_value": [ - "Logging", - "Metrics", - "AzureServices" - ] - }, - "default_action": { - "constant_value": "Deny" - } - } - ], - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_subnet.example", - "mode": "managed", - "type": "azurerm_subnet", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "address_prefix": { - "constant_value": "10.0.2.0/24" - }, - "name": { - "constant_value": "subnetname" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "service_endpoints": { - "constant_value": [ - "Microsoft.Sql", - "Microsoft.Storage" - ] - }, - "virtual_network_name": { - "references": [ - "azurerm_virtual_network.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_virtual_network.example", - "mode": "managed", - "type": "azurerm_virtual_network", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "address_space": { - "constant_value": [ - "10.0.0.0/16" - ] - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "virtnetname" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("account_microsoft_services_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tfplan b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tfplan new file mode 100644 index 00000000..b9e92655 --- /dev/null +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.tfplan @@ -0,0 +1,933 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount1", + "network_rules": [ + { + "default_action": "Allow" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount2", + "network_rules": [ + { + "bypass": [ + "Logging", + "Metrics" + ], + "default_action": "Deny" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount1", + "network_rules": [ + { + "bypass": [ + "AzureServices" + ], + "default_action": "Deny", + "ip_rules": [ + "100.0.0.1" + ] + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.validstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount2", + "network_rules": [ + { + "bypass": [ + "AzureServices", + "Logging", + "Metrics" + ], + "default_action": "Deny" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_subnet.example", + "mode": "managed", + "type": "azurerm_subnet", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "address_prefix": "10.0.2.0/24", + "delegation": [], + "enforce_private_link_endpoint_network_policies": false, + "enforce_private_link_service_network_policies": false, + "name": "subnetname", + "resource_group_name": "example-resources", + "service_endpoint_policy_ids": null, + "service_endpoints": [ + "Microsoft.Sql", + "Microsoft.Storage" + ], + "timeouts": null, + "virtual_network_name": "virtnetname" + } + }, + { + "address": "azurerm_virtual_network.example", + "mode": "managed", + "type": "azurerm_virtual_network", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "address_space": [ + "10.0.0.0/16" + ], + "bgp_community": null, + "ddos_protection_plan": [], + "dns_servers": null, + "location": "westeurope", + "name": "virtnetname", + "resource_group_name": "example-resources", + "tags": null, + "timeouts": null, + "vm_protection_enabled": false + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount1", + "network_rules": [ + { + "default_action": "Allow" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": [ + { + "bypass": true, + "ip_rules": true, + "virtual_network_subnet_ids": true + } + ], + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount2", + "network_rules": [ + { + "bypass": [ + "Logging", + "Metrics" + ], + "default_action": "Deny" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": [ + { + "bypass": [ + false, + false + ], + "ip_rules": true, + "virtual_network_subnet_ids": true + } + ], + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount1", + "network_rules": [ + { + "bypass": [ + "AzureServices" + ], + "default_action": "Deny", + "ip_rules": [ + "100.0.0.1" + ] + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": [ + { + "bypass": [ + false + ], + "ip_rules": [ + false + ], + "virtual_network_subnet_ids": true + } + ], + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_storage_account.validstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount2", + "network_rules": [ + { + "bypass": [ + "AzureServices", + "Logging", + "Metrics" + ], + "default_action": "Deny" + } + ], + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": [ + { + "bypass": [ + false, + false, + false + ], + "ip_rules": true, + "virtual_network_subnet_ids": true + } + ], + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + }, + { + "address": "azurerm_subnet.example", + "mode": "managed", + "type": "azurerm_subnet", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "address_prefix": "10.0.2.0/24", + "delegation": [], + "enforce_private_link_endpoint_network_policies": false, + "enforce_private_link_service_network_policies": false, + "name": "subnetname", + "resource_group_name": "example-resources", + "service_endpoint_policy_ids": null, + "service_endpoints": [ + "Microsoft.Sql", + "Microsoft.Storage" + ], + "timeouts": null, + "virtual_network_name": "virtnetname" + }, + "after_unknown": { + "address_prefixes": true, + "delegation": [], + "id": true, + "service_endpoints": [ + false, + false + ] + } + } + }, + { + "address": "azurerm_virtual_network.example", + "mode": "managed", + "type": "azurerm_virtual_network", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "address_space": [ + "10.0.0.0/16" + ], + "bgp_community": null, + "ddos_protection_plan": [], + "dns_servers": null, + "location": "westeurope", + "name": "virtnetname", + "resource_group_name": "example-resources", + "tags": null, + "timeouts": null, + "vm_protection_enabled": false + }, + "after_unknown": { + "address_space": [ + false + ], + "ddos_protection_plan": [], + "guid": true, + "id": true, + "subnet": true + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West Europe" + }, + "name": { + "constant_value": "example-resources" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidstorageaccount1" + }, + "network_rules": [ + { + "default_action": { + "constant_value": "Allow" + } + } + ], + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidstorageaccount2" + }, + "network_rules": [ + { + "bypass": { + "constant_value": [ + "Logging", + "Metrics" + ] + }, + "default_action": { + "constant_value": "Deny" + } + } + ], + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "enable_https_traffic_only": { + "constant_value": true + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "validstorageaccount1" + }, + "network_rules": [ + { + "bypass": { + "constant_value": [ + "AzureServices" + ] + }, + "default_action": { + "constant_value": "Deny" + }, + "ip_rules": { + "constant_value": [ + "100.0.0.1" + ] + }, + "virtual_network_subnet_ids": { + "references": [ + "azurerm_subnet.example" + ] + } + } + ], + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.validstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount2", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "enable_https_traffic_only": { + "constant_value": true + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "validstorageaccount2" + }, + "network_rules": [ + { + "bypass": { + "constant_value": [ + "Logging", + "Metrics", + "AzureServices" + ] + }, + "default_action": { + "constant_value": "Deny" + } + } + ], + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_subnet.example", + "mode": "managed", + "type": "azurerm_subnet", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "address_prefix": { + "constant_value": "10.0.2.0/24" + }, + "name": { + "constant_value": "subnetname" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "service_endpoints": { + "constant_value": [ + "Microsoft.Sql", + "Microsoft.Storage" + ] + }, + "virtual_network_name": { + "references": [ + "azurerm_virtual_network.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_virtual_network.example", + "mode": "managed", + "type": "azurerm_virtual_network", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "address_space": { + "constant_value": [ + "10.0.0.0/16" + ] + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "virtnetname" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego index c1b75f24..6abe8d34 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,510 +16,17 @@ # # tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.account_secure_transfer_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": false, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount1", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": false, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount2", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount1", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": false, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount1", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": true, - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [], - "tags": {} - } - } - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": false, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "invalidstorageaccount2", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": true, - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [], - "tags": {} - } - } - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "validstorageaccount1", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": true, - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [], - "tags": {} - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West Europe" - }, - "name": { - "constant_value": "example-resources" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_storage_account.invalidstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount1", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "GRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "enable_https_traffic_only": { - "constant_value": false - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidstorageaccount1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "tags": { - "constant_value": { - "environment": "staging" - } - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.invalidstorageaccount2", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "invalidstorageaccount2", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "GRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "enable_https_traffic_only": { - "constant_value": false - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "invalidstorageaccount2" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "tags": { - "constant_value": { - "environment": "staging" - } - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_account.validstorageaccount1", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "validstorageaccount1", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "GRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "enable_https_traffic_only": { - "constant_value": true - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "validstorageaccount1" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "tags": { - "constant_value": { - "environment": "staging" - } - } - }, - "schema_version": 2 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("account_secure_transfer_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tfplan b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tfplan new file mode 100644 index 00000000..2f0a83fe --- /dev/null +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.tfplan @@ -0,0 +1,500 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": false, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount1", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": false, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount2", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount1", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": false, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount1", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": true, + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [], + "tags": {} + } + } + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": false, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "invalidstorageaccount2", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": true, + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [], + "tags": {} + } + } + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "validstorageaccount1", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": true, + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [], + "tags": {} + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West Europe" + }, + "name": { + "constant_value": "example-resources" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_storage_account.invalidstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount1", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "GRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "enable_https_traffic_only": { + "constant_value": false + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidstorageaccount1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "tags": { + "constant_value": { + "environment": "staging" + } + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.invalidstorageaccount2", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "invalidstorageaccount2", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "GRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "enable_https_traffic_only": { + "constant_value": false + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "invalidstorageaccount2" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "tags": { + "constant_value": { + "environment": "staging" + } + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_account.validstorageaccount1", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "validstorageaccount1", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "GRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "enable_https_traffic_only": { + "constant_value": true + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "validstorageaccount1" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "tags": { + "constant_value": { + "environment": "staging" + } + } + }, + "schema_version": 2 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego index 02242ca5..bd558f1c 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,411 +16,17 @@ # # tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.container_private_access_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.example", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "example", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "examplestoraccount", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - } - }, - { - "address": "azurerm_storage_container.invalidcontainer1", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "invalidcontainer1", - "provider_name": "azurerm", - "schema_version": 1, - "values": { - "container_access_type": "container", - "name": "invalidcontainer1", - "storage_account_name": "examplestoraccount", - "timeouts": null - } - }, - { - "address": "azurerm_storage_container.validcontainer1", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "validcontainer1", - "provider_name": "azurerm", - "schema_version": 1, - "values": { - "container_access_type": "private", - "name": "validcontainer1", - "storage_account_name": "examplestoraccount", - "timeouts": null - } - }, - { - "address": "azurerm_storage_container.validcontainer2", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "validcontainer2", - "provider_name": "azurerm", - "schema_version": 1, - "values": { - "container_access_type": "private", - "name": "validcontainer2", - "storage_account_name": "examplestoraccount", - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westeurope", - "name": "example-resources", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_storage_account.example", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "example", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "examplestoraccount", - "resource_group_name": "example-resources", - "static_website": [], - "tags": { - "environment": "staging" - }, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": true, - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [], - "tags": {} - } - } - }, - { - "address": "azurerm_storage_container.invalidcontainer1", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "invalidcontainer1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "container_access_type": "container", - "name": "invalidcontainer1", - "storage_account_name": "examplestoraccount", - "timeouts": null - }, - "after_unknown": { - "has_immutability_policy": true, - "has_legal_hold": true, - "id": true, - "metadata": true, - "resource_manager_id": true - } - } - }, - { - "address": "azurerm_storage_container.validcontainer1", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "validcontainer1", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "container_access_type": "private", - "name": "validcontainer1", - "storage_account_name": "examplestoraccount", - "timeouts": null - }, - "after_unknown": { - "has_immutability_policy": true, - "has_legal_hold": true, - "id": true, - "metadata": true, - "resource_manager_id": true - } - } - }, - { - "address": "azurerm_storage_container.validcontainer2", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "validcontainer2", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "container_access_type": "private", - "name": "validcontainer2", - "storage_account_name": "examplestoraccount", - "timeouts": null - }, - "after_unknown": { - "has_immutability_policy": true, - "has_legal_hold": true, - "id": true, - "metadata": true, - "resource_manager_id": true - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_resource_group.example", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West Europe" - }, - "name": { - "constant_value": "example-resources" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_storage_account.example", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "example", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "LRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "name": { - "constant_value": "examplestoraccount" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.example" - ] - }, - "tags": { - "constant_value": { - "environment": "staging" - } - } - }, - "schema_version": 2 - }, - { - "address": "azurerm_storage_container.invalidcontainer1", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "invalidcontainer1", - "provider_config_key": "azurerm", - "expressions": { - "container_access_type": { - "constant_value": "container" - }, - "name": { - "constant_value": "invalidcontainer1" - }, - "storage_account_name": { - "references": [ - "azurerm_storage_account.example" - ] - } - }, - "schema_version": 1 - }, - { - "address": "azurerm_storage_container.validcontainer1", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "validcontainer1", - "provider_config_key": "azurerm", - "expressions": { - "container_access_type": { - "constant_value": "private" - }, - "name": { - "constant_value": "validcontainer1" - }, - "storage_account_name": { - "references": [ - "azurerm_storage_account.example" - ] - } - }, - "schema_version": 1 - }, - { - "address": "azurerm_storage_container.validcontainer2", - "mode": "managed", - "type": "azurerm_storage_container", - "name": "validcontainer2", - "provider_config_key": "azurerm", - "expressions": { - "name": { - "constant_value": "validcontainer2" - }, - "storage_account_name": { - "references": [ - "azurerm_storage_account.example" - ] - } - }, - "schema_version": 1 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("container_private_access_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tfplan b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tfplan new file mode 100644 index 00000000..48852ae8 --- /dev/null +++ b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tfplan @@ -0,0 +1,397 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.example", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "examplestoraccount", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + } + }, + { + "address": "azurerm_storage_container.invalidcontainer1", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "invalidcontainer1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 1, + "values": { + "container_access_type": "container", + "name": "invalidcontainer1", + "storage_account_name": "examplestoraccount", + "timeouts": null + } + }, + { + "address": "azurerm_storage_container.validcontainer1", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "validcontainer1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 1, + "values": { + "container_access_type": "private", + "name": "validcontainer1", + "storage_account_name": "examplestoraccount", + "timeouts": null + } + }, + { + "address": "azurerm_storage_container.validcontainer2", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "validcontainer2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 1, + "values": { + "container_access_type": "private", + "name": "validcontainer2", + "storage_account_name": "examplestoraccount", + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westeurope", + "name": "example-resources", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_storage_account.example", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "LRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "examplestoraccount", + "nfsv3_enabled": false, + "resource_group_name": "example-resources", + "static_website": [], + "tags": { + "environment": "staging" + }, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": true, + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [], + "tags": {} + } + } + }, + { + "address": "azurerm_storage_container.invalidcontainer1", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "invalidcontainer1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_access_type": "container", + "name": "invalidcontainer1", + "storage_account_name": "examplestoraccount", + "timeouts": null + }, + "after_unknown": { + "has_immutability_policy": true, + "has_legal_hold": true, + "id": true, + "metadata": true, + "resource_manager_id": true + } + } + }, + { + "address": "azurerm_storage_container.validcontainer1", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "validcontainer1", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_access_type": "private", + "name": "validcontainer1", + "storage_account_name": "examplestoraccount", + "timeouts": null + }, + "after_unknown": { + "has_immutability_policy": true, + "has_legal_hold": true, + "id": true, + "metadata": true, + "resource_manager_id": true + } + } + }, + { + "address": "azurerm_storage_container.validcontainer2", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "validcontainer2", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "container_access_type": "private", + "name": "validcontainer2", + "storage_account_name": "examplestoraccount", + "timeouts": null + }, + "after_unknown": { + "has_immutability_policy": true, + "has_legal_hold": true, + "id": true, + "metadata": true, + "resource_manager_id": true + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_resource_group.example", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West Europe" + }, + "name": { + "constant_value": "example-resources" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_storage_account.example", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "example", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "LRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "name": { + "constant_value": "examplestoraccount" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.example" + ] + }, + "tags": { + "constant_value": { + "environment": "staging" + } + } + }, + "schema_version": 2 + }, + { + "address": "azurerm_storage_container.invalidcontainer1", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "invalidcontainer1", + "provider_config_key": "azurerm", + "expressions": { + "container_access_type": { + "constant_value": "container" + }, + "name": { + "constant_value": "invalidcontainer1" + }, + "storage_account_name": { + "references": [ + "azurerm_storage_account.example" + ] + } + }, + "schema_version": 1 + }, + { + "address": "azurerm_storage_container.validcontainer1", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "validcontainer1", + "provider_config_key": "azurerm", + "expressions": { + "container_access_type": { + "constant_value": "private" + }, + "name": { + "constant_value": "validcontainer1" + }, + "storage_account_name": { + "references": [ + "azurerm_storage_account.example" + ] + } + }, + "schema_version": 1 + }, + { + "address": "azurerm_storage_container.validcontainer2", + "mode": "managed", + "type": "azurerm_storage_container", + "name": "validcontainer2", + "provider_config_key": "azurerm", + "expressions": { + "name": { + "constant_value": "validcontainer2" + }, + "storage_account_name": { + "references": [ + "azurerm_storage_account.example" + ] + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego index 474838dc..543e9217 100644 --- a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,676 +16,17 @@ # # tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.firewall_no_ingress_22_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "google_compute_firewall.invalid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-1", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "22", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_firewall.invalid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-2", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "20-24" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-2", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_firewall.valid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-1", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "80", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": [ - "web" - ], - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_firewall.valid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-2", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "22" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-2", - "network": "test-network", - "priority": 1000, - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_network.default", - "mode": "managed", - "type": "google_compute_network", - "name": "default", - "provider_name": "google", - "schema_version": 0, - "values": { - "auto_create_subnetworks": true, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "google_compute_firewall.invalid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "22", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false, - false, - false - ] - }, - { - "ports": [] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": [ - false - ] - } - } - }, - { - "address": "google_compute_firewall.invalid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-2", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "20-24" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-2", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false - ] - }, - { - "ports": [] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": [ - false - ] - } - } - }, - { - "address": "google_compute_firewall.valid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "80", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": [ - "web" - ], - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false, - false, - false - ] - }, - { - "ports": [] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": [ - false - ], - "source_tags": [ - false - ] - } - } - }, - { - "address": "google_compute_firewall.valid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-2", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "22" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-2", - "network": "test-network", - "priority": 1000, - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false - ] - }, - { - "ports": [] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": true - } - } - }, - { - "address": "google_compute_network.default", - "mode": "managed", - "type": "google_compute_network", - "name": "default", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "auto_create_subnetworks": true, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - }, - "after_unknown": { - "gateway_ipv4": true, - "id": true, - "project": true, - "routing_mode": true, - "self_link": true - } - } - } - ], - "configuration": { - "root_module": { - "resources": [ - { - "address": "google_compute_firewall.invalid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-1", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "protocol": { - "constant_value": "icmp" - } - }, - { - "ports": { - "constant_value": [ - "22", - "8080", - "1000-2000" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "invalid-rule-1" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - }, - "source_ranges": { - "constant_value": [ - "0.0.0.0/0" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_firewall.invalid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-2", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "protocol": { - "constant_value": "icmp" - } - }, - { - "ports": { - "constant_value": [ - "20-24" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "invalid-rule-2" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - }, - "source_ranges": { - "constant_value": [ - "0.0.0.0/0" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_firewall.valid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-1", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "protocol": { - "constant_value": "icmp" - } - }, - { - "ports": { - "constant_value": [ - "80", - "8080", - "1000-2000" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "valid-rule-1" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - }, - "source_ranges": { - "constant_value": [ - "0.0.0.0/0" - ] - }, - "source_tags": { - "constant_value": [ - "web" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_firewall.valid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-2", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "protocol": { - "constant_value": "icmp" - } - }, - { - "ports": { - "constant_value": [ - "22" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "valid-rule-2" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_network.default", - "mode": "managed", - "type": "google_compute_network", - "name": "default", - "provider_config_key": "google", - "expressions": { - "name": { - "constant_value": "test-network" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("firewall_no_ingress_22_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tfplan b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tfplan new file mode 100644 index 00000000..5f845638 --- /dev/null +++ b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.tfplan @@ -0,0 +1,661 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_compute_firewall.invalid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "22", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_firewall.invalid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "20-24" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-2", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_firewall.valid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "80", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": [ + "web" + ], + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_firewall.valid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "22" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-2", + "network": "test-network", + "priority": 1000, + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_network.default", + "mode": "managed", + "type": "google_compute_network", + "name": "default", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "auto_create_subnetworks": true, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_compute_firewall.invalid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "22", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false, + false, + false + ] + }, + { + "ports": [] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": [ + false + ] + } + } + }, + { + "address": "google_compute_firewall.invalid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "20-24" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-2", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false + ] + }, + { + "ports": [] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": [ + false + ] + } + } + }, + { + "address": "google_compute_firewall.valid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "80", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": [ + "web" + ], + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false, + false, + false + ] + }, + { + "ports": [] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": [ + false + ], + "source_tags": [ + false + ] + } + } + }, + { + "address": "google_compute_firewall.valid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "22" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-2", + "network": "test-network", + "priority": 1000, + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false + ] + }, + { + "ports": [] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": true + } + } + }, + { + "address": "google_compute_network.default", + "mode": "managed", + "type": "google_compute_network", + "name": "default", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "auto_create_subnetworks": true, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + }, + "after_unknown": { + "gateway_ipv4": true, + "id": true, + "mtu": true, + "project": true, + "routing_mode": true, + "self_link": true + } + } + } + ], + "configuration": { + "root_module": { + "resources": [ + { + "address": "google_compute_firewall.invalid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-1", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "protocol": { + "constant_value": "icmp" + } + }, + { + "ports": { + "constant_value": [ + "22", + "8080", + "1000-2000" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "invalid-rule-1" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + }, + "source_ranges": { + "constant_value": [ + "0.0.0.0/0" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_firewall.invalid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-2", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "protocol": { + "constant_value": "icmp" + } + }, + { + "ports": { + "constant_value": [ + "20-24" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "invalid-rule-2" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + }, + "source_ranges": { + "constant_value": [ + "0.0.0.0/0" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_firewall.valid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-1", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "protocol": { + "constant_value": "icmp" + } + }, + { + "ports": { + "constant_value": [ + "80", + "8080", + "1000-2000" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "valid-rule-1" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + }, + "source_ranges": { + "constant_value": [ + "0.0.0.0/0" + ] + }, + "source_tags": { + "constant_value": [ + "web" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_firewall.valid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-2", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "protocol": { + "constant_value": "icmp" + } + }, + { + "ports": { + "constant_value": [ + "22" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "valid-rule-2" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_network.default", + "mode": "managed", + "type": "google_compute_network", + "name": "default", + "provider_config_key": "google", + "expressions": { + "name": { + "constant_value": "test-network" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego index 1c9a19ca..41819093 100644 --- a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,664 +16,17 @@ # # tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.firewall_no_ingress_3389_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "google_compute_firewall.invalid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-1", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "3389", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_firewall.invalid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-2", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "3320-3395", - "20" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-2", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_firewall.valid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-1", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "80", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": [ - "web" - ], - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_firewall.valid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-2", - "provider_name": "google", - "schema_version": 1, - "values": { - "allow": [ - { - "ports": [ - "3389" - ], - "protocol": "tcp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-2", - "network": "test-network", - "priority": 1000, - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - } - }, - { - "address": "google_compute_network.default", - "mode": "managed", - "type": "google_compute_network", - "name": "default", - "provider_name": "google", - "schema_version": 0, - "values": { - "auto_create_subnetworks": true, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "google_compute_firewall.invalid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "3389", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false, - false, - false - ] - }, - { - "ports": [] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": [ - false - ] - } - } - }, - { - "address": "google_compute_firewall.invalid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-2", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "3320-3395", - "20" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "invalid-rule-2", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false, - false - ] - }, - { - "ports": [] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": [ - false - ] - } - } - }, - { - "address": "google_compute_firewall.valid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "80", - "8080", - "1000-2000" - ], - "protocol": "tcp" - }, - { - "ports": [], - "protocol": "icmp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-1", - "network": "test-network", - "priority": 1000, - "source_ranges": [ - "0.0.0.0/0" - ], - "source_service_accounts": null, - "source_tags": [ - "web" - ], - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false, - false, - false - ] - }, - { - "ports": [] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": [ - false - ], - "source_tags": [ - false - ] - } - } - }, - { - "address": "google_compute_firewall.valid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-2", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow": [ - { - "ports": [ - "3389" - ], - "protocol": "tcp" - } - ], - "deny": [], - "description": null, - "disabled": null, - "log_config": [], - "name": "valid-rule-2", - "network": "test-network", - "priority": 1000, - "source_service_accounts": null, - "source_tags": null, - "target_service_accounts": null, - "target_tags": null, - "timeouts": null - }, - "after_unknown": { - "allow": [ - { - "ports": [ - false - ] - } - ], - "creation_timestamp": true, - "deny": [], - "destination_ranges": true, - "direction": true, - "enable_logging": true, - "id": true, - "log_config": [], - "project": true, - "self_link": true, - "source_ranges": true - } - } - }, - { - "address": "google_compute_network.default", - "mode": "managed", - "type": "google_compute_network", - "name": "default", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "auto_create_subnetworks": true, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - }, - "after_unknown": { - "gateway_ipv4": true, - "id": true, - "project": true, - "routing_mode": true, - "self_link": true - } - } - } - ], - "configuration": { - "root_module": { - "resources": [ - { - "address": "google_compute_firewall.invalid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-1", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "protocol": { - "constant_value": "icmp" - } - }, - { - "ports": { - "constant_value": [ - "3389", - "8080", - "1000-2000" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "invalid-rule-1" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - }, - "source_ranges": { - "constant_value": [ - "0.0.0.0/0" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_firewall.invalid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "invalid-rule-2", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "protocol": { - "constant_value": "icmp" - } - }, - { - "ports": { - "constant_value": [ - "3320-3395", - "20" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "invalid-rule-2" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - }, - "source_ranges": { - "constant_value": [ - "0.0.0.0/0" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_firewall.valid-rule-1", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-1", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "protocol": { - "constant_value": "icmp" - } - }, - { - "ports": { - "constant_value": [ - "80", - "8080", - "1000-2000" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "valid-rule-1" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - }, - "source_ranges": { - "constant_value": [ - "0.0.0.0/0" - ] - }, - "source_tags": { - "constant_value": [ - "web" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_firewall.valid-rule-2", - "mode": "managed", - "type": "google_compute_firewall", - "name": "valid-rule-2", - "provider_config_key": "google", - "expressions": { - "allow": [ - { - "ports": { - "constant_value": [ - "3389" - ] - }, - "protocol": { - "constant_value": "tcp" - } - } - ], - "name": { - "constant_value": "valid-rule-2" - }, - "network": { - "references": [ - "google_compute_network.default" - ] - } - }, - "schema_version": 1 - }, - { - "address": "google_compute_network.default", - "mode": "managed", - "type": "google_compute_network", - "name": "default", - "provider_config_key": "google", - "expressions": { - "name": { - "constant_value": "test-network" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("firewall_no_ingress_3389_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tfplan b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tfplan new file mode 100644 index 00000000..7ee6831c --- /dev/null +++ b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.tfplan @@ -0,0 +1,649 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_compute_firewall.invalid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "3389", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_firewall.invalid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "3320-3395", + "20" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-2", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_firewall.valid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "80", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": [ + "web" + ], + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_firewall.valid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "allow": [ + { + "ports": [ + "3389" + ], + "protocol": "tcp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-2", + "network": "test-network", + "priority": 1000, + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + } + }, + { + "address": "google_compute_network.default", + "mode": "managed", + "type": "google_compute_network", + "name": "default", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "auto_create_subnetworks": true, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_compute_firewall.invalid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "3389", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false, + false, + false + ] + }, + { + "ports": [] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": [ + false + ] + } + } + }, + { + "address": "google_compute_firewall.invalid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "3320-3395", + "20" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "invalid-rule-2", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false, + false + ] + }, + { + "ports": [] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": [ + false + ] + } + } + }, + { + "address": "google_compute_firewall.valid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "80", + "8080", + "1000-2000" + ], + "protocol": "tcp" + }, + { + "ports": [], + "protocol": "icmp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-1", + "network": "test-network", + "priority": 1000, + "source_ranges": [ + "0.0.0.0/0" + ], + "source_service_accounts": null, + "source_tags": [ + "web" + ], + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false, + false, + false + ] + }, + { + "ports": [] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": [ + false + ], + "source_tags": [ + false + ] + } + } + }, + { + "address": "google_compute_firewall.valid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow": [ + { + "ports": [ + "3389" + ], + "protocol": "tcp" + } + ], + "deny": [], + "description": null, + "disabled": null, + "log_config": [], + "name": "valid-rule-2", + "network": "test-network", + "priority": 1000, + "source_service_accounts": null, + "source_tags": null, + "target_service_accounts": null, + "target_tags": null, + "timeouts": null + }, + "after_unknown": { + "allow": [ + { + "ports": [ + false + ] + } + ], + "creation_timestamp": true, + "deny": [], + "destination_ranges": true, + "direction": true, + "enable_logging": true, + "id": true, + "log_config": [], + "project": true, + "self_link": true, + "source_ranges": true + } + } + }, + { + "address": "google_compute_network.default", + "mode": "managed", + "type": "google_compute_network", + "name": "default", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "auto_create_subnetworks": true, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + }, + "after_unknown": { + "gateway_ipv4": true, + "id": true, + "mtu": true, + "project": true, + "routing_mode": true, + "self_link": true + } + } + } + ], + "configuration": { + "root_module": { + "resources": [ + { + "address": "google_compute_firewall.invalid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-1", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "protocol": { + "constant_value": "icmp" + } + }, + { + "ports": { + "constant_value": [ + "3389", + "8080", + "1000-2000" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "invalid-rule-1" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + }, + "source_ranges": { + "constant_value": [ + "0.0.0.0/0" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_firewall.invalid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "invalid-rule-2", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "protocol": { + "constant_value": "icmp" + } + }, + { + "ports": { + "constant_value": [ + "3320-3395", + "20" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "invalid-rule-2" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + }, + "source_ranges": { + "constant_value": [ + "0.0.0.0/0" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_firewall.valid-rule-1", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-1", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "protocol": { + "constant_value": "icmp" + } + }, + { + "ports": { + "constant_value": [ + "80", + "8080", + "1000-2000" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "valid-rule-1" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + }, + "source_ranges": { + "constant_value": [ + "0.0.0.0/0" + ] + }, + "source_tags": { + "constant_value": [ + "web" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_firewall.valid-rule-2", + "mode": "managed", + "type": "google_compute_firewall", + "name": "valid-rule-2", + "provider_config_key": "google", + "expressions": { + "allow": [ + { + "ports": { + "constant_value": [ + "3389" + ] + }, + "protocol": { + "constant_value": "tcp" + } + } + ], + "name": { + "constant_value": "valid-rule-2" + }, + "network": { + "references": [ + "google_compute_network.default" + ] + } + }, + "schema_version": 1 + }, + { + "address": "google_compute_network.default", + "mode": "managed", + "type": "google_compute_network", + "name": "default", + "provider_config_key": "google", + "expressions": { + "name": { + "constant_value": "test-network" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego index 03080f32..350dff57 100644 --- a/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,376 +16,17 @@ # # tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.subnet_flow_log_enabled_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "google_compute_network.custom-test", - "mode": "managed", - "type": "google_compute_network", - "name": "custom-test", - "provider_name": "google", - "schema_version": 0, - "values": { - "auto_create_subnetworks": false, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - } - }, - { - "address": "google_compute_subnetwork.invalid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-1", - "provider_name": "google", - "schema_version": 0, - "values": { - "description": null, - "ip_cidr_range": "10.0.0.0/16", - "log_config": [], - "name": "invalid-subnet-1", - "private_ip_google_access": false, - "region": "us-central1", - "timeouts": null - } - }, - { - "address": "google_compute_subnetwork.valid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-1", - "provider_name": "google", - "schema_version": 0, - "values": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [ - { - "aggregation_interval": "INTERVAL_10_MIN", - "filter_expr": "true", - "flow_sampling": 0.5, - "metadata": "INCLUDE_ALL_METADATA", - "metadata_fields": null - } - ], - "name": "valid-subnet-1", - "private_ip_google_access": true, - "region": "us-central1", - "timeouts": null - } - }, - { - "address": "google_compute_subnetwork.valid-subnet-2", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-2", - "provider_name": "google", - "schema_version": 0, - "values": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [ - { - "aggregation_interval": "INTERVAL_10_MIN", - "filter_expr": "true", - "flow_sampling": 0.5, - "metadata": "INCLUDE_ALL_METADATA", - "metadata_fields": null - } - ], - "name": "valid-subnet-2", - "private_ip_google_access": null, - "region": "us-central1", - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "google_compute_network.custom-test", - "mode": "managed", - "type": "google_compute_network", - "name": "custom-test", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "auto_create_subnetworks": false, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - }, - "after_unknown": { - "gateway_ipv4": true, - "id": true, - "project": true, - "routing_mode": true, - "self_link": true - } - } - }, - { - "address": "google_compute_subnetwork.invalid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": null, - "ip_cidr_range": "10.0.0.0/16", - "log_config": [], - "name": "invalid-subnet-1", - "private_ip_google_access": false, - "region": "us-central1", - "timeouts": null - }, - "after_unknown": { - "creation_timestamp": true, - "fingerprint": true, - "gateway_address": true, - "id": true, - "log_config": [], - "network": true, - "project": true, - "secondary_ip_range": true, - "self_link": true - } - } - }, - { - "address": "google_compute_subnetwork.valid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [ - { - "aggregation_interval": "INTERVAL_10_MIN", - "filter_expr": "true", - "flow_sampling": 0.5, - "metadata": "INCLUDE_ALL_METADATA", - "metadata_fields": null - } - ], - "name": "valid-subnet-1", - "private_ip_google_access": true, - "region": "us-central1", - "timeouts": null - }, - "after_unknown": { - "creation_timestamp": true, - "fingerprint": true, - "gateway_address": true, - "id": true, - "log_config": [ - {} - ], - "network": true, - "project": true, - "secondary_ip_range": true, - "self_link": true - } - } - }, - { - "address": "google_compute_subnetwork.valid-subnet-2", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-2", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [ - { - "aggregation_interval": "INTERVAL_10_MIN", - "filter_expr": "true", - "flow_sampling": 0.5, - "metadata": "INCLUDE_ALL_METADATA", - "metadata_fields": null - } - ], - "name": "valid-subnet-2", - "private_ip_google_access": null, - "region": "us-central1", - "timeouts": null - }, - "after_unknown": { - "creation_timestamp": true, - "fingerprint": true, - "gateway_address": true, - "id": true, - "log_config": [ - {} - ], - "network": true, - "project": true, - "secondary_ip_range": true, - "self_link": true - } - } - } - ], - "configuration": { - "root_module": { - "resources": [ - { - "address": "google_compute_network.custom-test", - "mode": "managed", - "type": "google_compute_network", - "name": "custom-test", - "provider_config_key": "google", - "expressions": { - "auto_create_subnetworks": { - "constant_value": false - }, - "name": { - "constant_value": "test-network" - } - }, - "schema_version": 0 - }, - { - "address": "google_compute_subnetwork.invalid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-1", - "provider_config_key": "google", - "expressions": { - "ip_cidr_range": { - "constant_value": "10.0.0.0/16" - }, - "name": { - "constant_value": "invalid-subnet-1" - }, - "network": { - "references": [ - "google_compute_network.custom-test" - ] - }, - "private_ip_google_access": { - "constant_value": false - }, - "region": { - "constant_value": "us-central1" - } - }, - "schema_version": 0 - }, - { - "address": "google_compute_subnetwork.valid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-1", - "provider_config_key": "google", - "expressions": { - "ip_cidr_range": { - "constant_value": "10.2.0.0/16" - }, - "log_config": [ - { - "aggregation_interval": { - "constant_value": "INTERVAL_10_MIN" - }, - "flow_sampling": { - "constant_value": 0.5 - }, - "metadata": { - "constant_value": "INCLUDE_ALL_METADATA" - } - } - ], - "name": { - "constant_value": "valid-subnet-1" - }, - "network": { - "references": [ - "google_compute_network.custom-test" - ] - }, - "private_ip_google_access": { - "constant_value": true - }, - "region": { - "constant_value": "us-central1" - } - }, - "schema_version": 0 - }, - { - "address": "google_compute_subnetwork.valid-subnet-2", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-2", - "provider_config_key": "google", - "expressions": { - "ip_cidr_range": { - "constant_value": "10.2.0.0/16" - }, - "log_config": [ - { - "aggregation_interval": { - "constant_value": "INTERVAL_10_MIN" - } - } - ], - "name": { - "constant_value": "valid-subnet-2" - }, - "network": { - "references": [ - "google_compute_network.custom-test" - ] - }, - "region": { - "constant_value": "us-central1" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("subnet_flow_log_enabled_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tfplan b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tfplan new file mode 100644 index 00000000..f4031a25 --- /dev/null +++ b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.tfplan @@ -0,0 +1,364 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_compute_network.custom-test", + "mode": "managed", + "type": "google_compute_network", + "name": "custom-test", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "auto_create_subnetworks": false, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + } + }, + { + "address": "google_compute_subnetwork.invalid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "ip_cidr_range": "10.0.0.0/16", + "log_config": [], + "name": "invalid-subnet-1", + "private_ip_google_access": false, + "region": "us-central1", + "timeouts": null + } + }, + { + "address": "google_compute_subnetwork.valid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [ + { + "aggregation_interval": "INTERVAL_10_MIN", + "filter_expr": "true", + "flow_sampling": 0.5, + "metadata": "INCLUDE_ALL_METADATA", + "metadata_fields": null + } + ], + "name": "valid-subnet-1", + "private_ip_google_access": true, + "region": "us-central1", + "timeouts": null + } + }, + { + "address": "google_compute_subnetwork.valid-subnet-2", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [ + { + "aggregation_interval": "INTERVAL_10_MIN", + "filter_expr": "true", + "flow_sampling": 0.5, + "metadata": "INCLUDE_ALL_METADATA", + "metadata_fields": null + } + ], + "name": "valid-subnet-2", + "private_ip_google_access": null, + "region": "us-central1", + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_compute_network.custom-test", + "mode": "managed", + "type": "google_compute_network", + "name": "custom-test", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "auto_create_subnetworks": false, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + }, + "after_unknown": { + "gateway_ipv4": true, + "id": true, + "mtu": true, + "project": true, + "routing_mode": true, + "self_link": true + } + } + }, + { + "address": "google_compute_subnetwork.invalid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "ip_cidr_range": "10.0.0.0/16", + "log_config": [], + "name": "invalid-subnet-1", + "private_ip_google_access": false, + "region": "us-central1", + "timeouts": null + }, + "after_unknown": { + "creation_timestamp": true, + "fingerprint": true, + "gateway_address": true, + "id": true, + "log_config": [], + "network": true, + "private_ipv6_google_access": true, + "project": true, + "secondary_ip_range": true, + "self_link": true + } + } + }, + { + "address": "google_compute_subnetwork.valid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [ + { + "aggregation_interval": "INTERVAL_10_MIN", + "filter_expr": "true", + "flow_sampling": 0.5, + "metadata": "INCLUDE_ALL_METADATA", + "metadata_fields": null + } + ], + "name": "valid-subnet-1", + "private_ip_google_access": true, + "region": "us-central1", + "timeouts": null + }, + "after_unknown": { + "creation_timestamp": true, + "fingerprint": true, + "gateway_address": true, + "id": true, + "log_config": [ + {} + ], + "network": true, + "private_ipv6_google_access": true, + "project": true, + "secondary_ip_range": true, + "self_link": true + } + } + }, + { + "address": "google_compute_subnetwork.valid-subnet-2", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [ + { + "aggregation_interval": "INTERVAL_10_MIN", + "filter_expr": "true", + "flow_sampling": 0.5, + "metadata": "INCLUDE_ALL_METADATA", + "metadata_fields": null + } + ], + "name": "valid-subnet-2", + "private_ip_google_access": null, + "region": "us-central1", + "timeouts": null + }, + "after_unknown": { + "creation_timestamp": true, + "fingerprint": true, + "gateway_address": true, + "id": true, + "log_config": [ + {} + ], + "network": true, + "private_ipv6_google_access": true, + "project": true, + "secondary_ip_range": true, + "self_link": true + } + } + } + ], + "configuration": { + "root_module": { + "resources": [ + { + "address": "google_compute_network.custom-test", + "mode": "managed", + "type": "google_compute_network", + "name": "custom-test", + "provider_config_key": "google", + "expressions": { + "auto_create_subnetworks": { + "constant_value": false + }, + "name": { + "constant_value": "test-network" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_subnetwork.invalid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-1", + "provider_config_key": "google", + "expressions": { + "ip_cidr_range": { + "constant_value": "10.0.0.0/16" + }, + "name": { + "constant_value": "invalid-subnet-1" + }, + "network": { + "references": [ + "google_compute_network.custom-test" + ] + }, + "private_ip_google_access": { + "constant_value": false + }, + "region": { + "constant_value": "us-central1" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_subnetwork.valid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-1", + "provider_config_key": "google", + "expressions": { + "ip_cidr_range": { + "constant_value": "10.2.0.0/16" + }, + "log_config": [ + { + "aggregation_interval": { + "constant_value": "INTERVAL_10_MIN" + }, + "flow_sampling": { + "constant_value": 0.5 + }, + "metadata": { + "constant_value": "INCLUDE_ALL_METADATA" + } + } + ], + "name": { + "constant_value": "valid-subnet-1" + }, + "network": { + "references": [ + "google_compute_network.custom-test" + ] + }, + "private_ip_google_access": { + "constant_value": true + }, + "region": { + "constant_value": "us-central1" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_subnetwork.valid-subnet-2", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-2", + "provider_config_key": "google", + "expressions": { + "ip_cidr_range": { + "constant_value": "10.2.0.0/16" + }, + "log_config": [ + { + "aggregation_interval": { + "constant_value": "INTERVAL_10_MIN" + } + } + ], + "name": { + "constant_value": "valid-subnet-2" + }, + "network": { + "references": [ + "google_compute_network.custom-test" + ] + }, + "region": { + "constant_value": "us-central1" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego index a92e053a..6c4331da 100644 --- a/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,320 +16,17 @@ # # tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.subnet_private_google_access_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "google_compute_network.custom-test", - "mode": "managed", - "type": "google_compute_network", - "name": "custom-test", - "provider_name": "google", - "schema_version": 0, - "values": { - "auto_create_subnetworks": false, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - } - }, - { - "address": "google_compute_subnetwork.invalid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-1", - "provider_name": "google", - "schema_version": 0, - "values": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [], - "name": "invalid-subnet-1", - "private_ip_google_access": null, - "region": "us-central1", - "timeouts": null - } - }, - { - "address": "google_compute_subnetwork.invalid-subnet-2", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-2", - "provider_name": "google", - "schema_version": 0, - "values": { - "description": null, - "ip_cidr_range": "10.0.0.0/16", - "log_config": [], - "name": "invalid-subnet-2", - "private_ip_google_access": false, - "region": "us-central1", - "timeouts": null - } - }, - { - "address": "google_compute_subnetwork.valid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-1", - "provider_name": "google", - "schema_version": 0, - "values": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [], - "name": "valid-subnet-1", - "private_ip_google_access": true, - "region": "us-central1", - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "google_compute_network.custom-test", - "mode": "managed", - "type": "google_compute_network", - "name": "custom-test", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "auto_create_subnetworks": false, - "delete_default_routes_on_create": false, - "description": null, - "name": "test-network", - "timeouts": null - }, - "after_unknown": { - "gateway_ipv4": true, - "id": true, - "project": true, - "routing_mode": true, - "self_link": true - } - } - }, - { - "address": "google_compute_subnetwork.invalid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [], - "name": "invalid-subnet-1", - "private_ip_google_access": null, - "region": "us-central1", - "timeouts": null - }, - "after_unknown": { - "creation_timestamp": true, - "fingerprint": true, - "gateway_address": true, - "id": true, - "log_config": [], - "network": true, - "project": true, - "secondary_ip_range": true, - "self_link": true - } - } - }, - { - "address": "google_compute_subnetwork.invalid-subnet-2", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-2", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": null, - "ip_cidr_range": "10.0.0.0/16", - "log_config": [], - "name": "invalid-subnet-2", - "private_ip_google_access": false, - "region": "us-central1", - "timeouts": null - }, - "after_unknown": { - "creation_timestamp": true, - "fingerprint": true, - "gateway_address": true, - "id": true, - "log_config": [], - "network": true, - "project": true, - "secondary_ip_range": true, - "self_link": true - } - } - }, - { - "address": "google_compute_subnetwork.valid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": null, - "ip_cidr_range": "10.2.0.0/16", - "log_config": [], - "name": "valid-subnet-1", - "private_ip_google_access": true, - "region": "us-central1", - "timeouts": null - }, - "after_unknown": { - "creation_timestamp": true, - "fingerprint": true, - "gateway_address": true, - "id": true, - "log_config": [], - "network": true, - "project": true, - "secondary_ip_range": true, - "self_link": true - } - } - } - ], - "configuration": { - "root_module": { - "resources": [ - { - "address": "google_compute_network.custom-test", - "mode": "managed", - "type": "google_compute_network", - "name": "custom-test", - "provider_config_key": "google", - "expressions": { - "auto_create_subnetworks": { - "constant_value": false - }, - "name": { - "constant_value": "test-network" - } - }, - "schema_version": 0 - }, - { - "address": "google_compute_subnetwork.invalid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-1", - "provider_config_key": "google", - "expressions": { - "ip_cidr_range": { - "constant_value": "10.2.0.0/16" - }, - "name": { - "constant_value": "invalid-subnet-1" - }, - "network": { - "references": [ - "google_compute_network.custom-test" - ] - }, - "region": { - "constant_value": "us-central1" - } - }, - "schema_version": 0 - }, - { - "address": "google_compute_subnetwork.invalid-subnet-2", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "invalid-subnet-2", - "provider_config_key": "google", - "expressions": { - "ip_cidr_range": { - "constant_value": "10.0.0.0/16" - }, - "name": { - "constant_value": "invalid-subnet-2" - }, - "network": { - "references": [ - "google_compute_network.custom-test" - ] - }, - "private_ip_google_access": { - "constant_value": false - }, - "region": { - "constant_value": "us-central1" - } - }, - "schema_version": 0 - }, - { - "address": "google_compute_subnetwork.valid-subnet-1", - "mode": "managed", - "type": "google_compute_subnetwork", - "name": "valid-subnet-1", - "provider_config_key": "google", - "expressions": { - "ip_cidr_range": { - "constant_value": "10.2.0.0/16" - }, - "name": { - "constant_value": "valid-subnet-1" - }, - "network": { - "references": [ - "google_compute_network.custom-test" - ] - }, - "private_ip_google_access": { - "constant_value": true - }, - "region": { - "constant_value": "us-central1" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("subnet_private_google_access_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tfplan b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tfplan new file mode 100644 index 00000000..5851e51e --- /dev/null +++ b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.tfplan @@ -0,0 +1,308 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_compute_network.custom-test", + "mode": "managed", + "type": "google_compute_network", + "name": "custom-test", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "auto_create_subnetworks": false, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + } + }, + { + "address": "google_compute_subnetwork.invalid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [], + "name": "invalid-subnet-1", + "private_ip_google_access": null, + "region": "us-central1", + "timeouts": null + } + }, + { + "address": "google_compute_subnetwork.invalid-subnet-2", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "ip_cidr_range": "10.0.0.0/16", + "log_config": [], + "name": "invalid-subnet-2", + "private_ip_google_access": false, + "region": "us-central1", + "timeouts": null + } + }, + { + "address": "google_compute_subnetwork.valid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [], + "name": "valid-subnet-1", + "private_ip_google_access": true, + "region": "us-central1", + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_compute_network.custom-test", + "mode": "managed", + "type": "google_compute_network", + "name": "custom-test", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "auto_create_subnetworks": false, + "delete_default_routes_on_create": false, + "description": null, + "name": "test-network", + "timeouts": null + }, + "after_unknown": { + "gateway_ipv4": true, + "id": true, + "mtu": true, + "project": true, + "routing_mode": true, + "self_link": true + } + } + }, + { + "address": "google_compute_subnetwork.invalid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [], + "name": "invalid-subnet-1", + "private_ip_google_access": null, + "region": "us-central1", + "timeouts": null + }, + "after_unknown": { + "creation_timestamp": true, + "fingerprint": true, + "gateway_address": true, + "id": true, + "log_config": [], + "network": true, + "private_ipv6_google_access": true, + "project": true, + "secondary_ip_range": true, + "self_link": true + } + } + }, + { + "address": "google_compute_subnetwork.invalid-subnet-2", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-2", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "ip_cidr_range": "10.0.0.0/16", + "log_config": [], + "name": "invalid-subnet-2", + "private_ip_google_access": false, + "region": "us-central1", + "timeouts": null + }, + "after_unknown": { + "creation_timestamp": true, + "fingerprint": true, + "gateway_address": true, + "id": true, + "log_config": [], + "network": true, + "private_ipv6_google_access": true, + "project": true, + "secondary_ip_range": true, + "self_link": true + } + } + }, + { + "address": "google_compute_subnetwork.valid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "ip_cidr_range": "10.2.0.0/16", + "log_config": [], + "name": "valid-subnet-1", + "private_ip_google_access": true, + "region": "us-central1", + "timeouts": null + }, + "after_unknown": { + "creation_timestamp": true, + "fingerprint": true, + "gateway_address": true, + "id": true, + "log_config": [], + "network": true, + "private_ipv6_google_access": true, + "project": true, + "secondary_ip_range": true, + "self_link": true + } + } + } + ], + "configuration": { + "root_module": { + "resources": [ + { + "address": "google_compute_network.custom-test", + "mode": "managed", + "type": "google_compute_network", + "name": "custom-test", + "provider_config_key": "google", + "expressions": { + "auto_create_subnetworks": { + "constant_value": false + }, + "name": { + "constant_value": "test-network" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_subnetwork.invalid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-1", + "provider_config_key": "google", + "expressions": { + "ip_cidr_range": { + "constant_value": "10.2.0.0/16" + }, + "name": { + "constant_value": "invalid-subnet-1" + }, + "network": { + "references": [ + "google_compute_network.custom-test" + ] + }, + "region": { + "constant_value": "us-central1" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_subnetwork.invalid-subnet-2", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "invalid-subnet-2", + "provider_config_key": "google", + "expressions": { + "ip_cidr_range": { + "constant_value": "10.0.0.0/16" + }, + "name": { + "constant_value": "invalid-subnet-2" + }, + "network": { + "references": [ + "google_compute_network.custom-test" + ] + }, + "private_ip_google_access": { + "constant_value": false + }, + "region": { + "constant_value": "us-central1" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_subnetwork.valid-subnet-1", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "valid-subnet-1", + "provider_config_key": "google", + "expressions": { + "ip_cidr_range": { + "constant_value": "10.2.0.0/16" + }, + "name": { + "constant_value": "valid-subnet-1" + }, + "network": { + "references": [ + "google_compute_network.custom-test" + ] + }, + "private_ip_google_access": { + "constant_value": true + }, + "region": { + "constant_value": "us-central1" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego index 04b1cb3c..f16dc51a 100644 --- a/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego +++ b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,269 +16,17 @@ # # tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.kms.inputs.cryptokey_rotate_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "google_kms_crypto_key.invalid_key_1", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "invalid_key_1", - "provider_name": "google", - "schema_version": 1, - "values": { - "labels": null, - "name": "crypto-key-example", - "purpose": "ENCRYPT_DECRYPT", - "rotation_period": null, - "timeouts": null - } - }, - { - "address": "google_kms_crypto_key.invalid_key_2", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "invalid_key_2", - "provider_name": "google", - "schema_version": 1, - "values": { - "labels": null, - "name": "crypto-key-example", - "purpose": "ENCRYPT_DECRYPT", - "rotation_period": "31536002s", - "timeouts": null - } - }, - { - "address": "google_kms_crypto_key.valid_key_1", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "valid_key_1", - "provider_name": "google", - "schema_version": 1, - "values": { - "labels": null, - "name": "crypto-key-example", - "purpose": "ENCRYPT_DECRYPT", - "rotation_period": "31536000s", - "timeouts": null - } - }, - { - "address": "google_kms_key_ring.keyring", - "mode": "managed", - "type": "google_kms_key_ring", - "name": "keyring", - "provider_name": "google", - "schema_version": 0, - "values": { - "location": "global", - "name": "keyring-example", - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "google_kms_crypto_key.invalid_key_1", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "invalid_key_1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "labels": null, - "name": "crypto-key-example", - "purpose": "ENCRYPT_DECRYPT", - "rotation_period": null, - "timeouts": null - }, - "after_unknown": { - "id": true, - "key_ring": true, - "self_link": true, - "version_template": true - } - } - }, - { - "address": "google_kms_crypto_key.invalid_key_2", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "invalid_key_2", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "labels": null, - "name": "crypto-key-example", - "purpose": "ENCRYPT_DECRYPT", - "rotation_period": "31536002s", - "timeouts": null - }, - "after_unknown": { - "id": true, - "key_ring": true, - "self_link": true, - "version_template": true - } - } - }, - { - "address": "google_kms_crypto_key.valid_key_1", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "valid_key_1", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "labels": null, - "name": "crypto-key-example", - "purpose": "ENCRYPT_DECRYPT", - "rotation_period": "31536000s", - "timeouts": null - }, - "after_unknown": { - "id": true, - "key_ring": true, - "self_link": true, - "version_template": true - } - } - }, - { - "address": "google_kms_key_ring.keyring", - "mode": "managed", - "type": "google_kms_key_ring", - "name": "keyring", - "provider_name": "google", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "global", - "name": "keyring-example", - "timeouts": null - }, - "after_unknown": { - "id": true, - "project": true, - "self_link": true - } - } - } - ], - "configuration": { - "root_module": { - "resources": [ - { - "address": "google_kms_crypto_key.invalid_key_1", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "invalid_key_1", - "provider_config_key": "google", - "expressions": { - "key_ring": { - "references": [ - "google_kms_key_ring.keyring" - ] - }, - "name": { - "constant_value": "crypto-key-example" - } - }, - "schema_version": 1 - }, - { - "address": "google_kms_crypto_key.invalid_key_2", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "invalid_key_2", - "provider_config_key": "google", - "expressions": { - "key_ring": { - "references": [ - "google_kms_key_ring.keyring" - ] - }, - "name": { - "constant_value": "crypto-key-example" - }, - "rotation_period": { - "constant_value": "31536002s" - } - }, - "schema_version": 1 - }, - { - "address": "google_kms_crypto_key.valid_key_1", - "mode": "managed", - "type": "google_kms_crypto_key", - "name": "valid_key_1", - "provider_config_key": "google", - "expressions": { - "key_ring": { - "references": [ - "google_kms_key_ring.keyring" - ] - }, - "name": { - "constant_value": "crypto-key-example" - }, - "rotation_period": { - "constant_value": "31536000s" - } - }, - "schema_version": 1 - }, - { - "address": "google_kms_key_ring.keyring", - "mode": "managed", - "type": "google_kms_key_ring", - "name": "keyring", - "provider_config_key": "google", - "expressions": { - "location": { - "constant_value": "global" - }, - "name": { - "constant_value": "keyring-example" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("cryptokey_rotate_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tfplan b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tfplan new file mode 100644 index 00000000..6b4d0b19 --- /dev/null +++ b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.tfplan @@ -0,0 +1,259 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_kms_crypto_key.invalid_key_1", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "invalid_key_1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "labels": null, + "name": "crypto-key-example", + "purpose": "ENCRYPT_DECRYPT", + "rotation_period": null, + "skip_initial_version_creation": false, + "timeouts": null + } + }, + { + "address": "google_kms_crypto_key.invalid_key_2", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "invalid_key_2", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "labels": null, + "name": "crypto-key-example", + "purpose": "ENCRYPT_DECRYPT", + "rotation_period": "31536002s", + "skip_initial_version_creation": false, + "timeouts": null + } + }, + { + "address": "google_kms_crypto_key.valid_key_1", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "valid_key_1", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "labels": null, + "name": "crypto-key-example", + "purpose": "ENCRYPT_DECRYPT", + "rotation_period": "31536000s", + "skip_initial_version_creation": false, + "timeouts": null + } + }, + { + "address": "google_kms_key_ring.keyring", + "mode": "managed", + "type": "google_kms_key_ring", + "name": "keyring", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "location": "global", + "name": "keyring-example", + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_kms_crypto_key.invalid_key_1", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "invalid_key_1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "labels": null, + "name": "crypto-key-example", + "purpose": "ENCRYPT_DECRYPT", + "rotation_period": null, + "skip_initial_version_creation": false, + "timeouts": null + }, + "after_unknown": { + "id": true, + "key_ring": true, + "self_link": true, + "version_template": true + } + } + }, + { + "address": "google_kms_crypto_key.invalid_key_2", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "invalid_key_2", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "labels": null, + "name": "crypto-key-example", + "purpose": "ENCRYPT_DECRYPT", + "rotation_period": "31536002s", + "skip_initial_version_creation": false, + "timeouts": null + }, + "after_unknown": { + "id": true, + "key_ring": true, + "self_link": true, + "version_template": true + } + } + }, + { + "address": "google_kms_crypto_key.valid_key_1", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "valid_key_1", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "labels": null, + "name": "crypto-key-example", + "purpose": "ENCRYPT_DECRYPT", + "rotation_period": "31536000s", + "skip_initial_version_creation": false, + "timeouts": null + }, + "after_unknown": { + "id": true, + "key_ring": true, + "self_link": true, + "version_template": true + } + } + }, + { + "address": "google_kms_key_ring.keyring", + "mode": "managed", + "type": "google_kms_key_ring", + "name": "keyring", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "global", + "name": "keyring-example", + "timeouts": null + }, + "after_unknown": { + "id": true, + "project": true, + "self_link": true + } + } + } + ], + "configuration": { + "root_module": { + "resources": [ + { + "address": "google_kms_crypto_key.invalid_key_1", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "invalid_key_1", + "provider_config_key": "google", + "expressions": { + "key_ring": { + "references": [ + "google_kms_key_ring.keyring" + ] + }, + "name": { + "constant_value": "crypto-key-example" + } + }, + "schema_version": 1 + }, + { + "address": "google_kms_crypto_key.invalid_key_2", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "invalid_key_2", + "provider_config_key": "google", + "expressions": { + "key_ring": { + "references": [ + "google_kms_key_ring.keyring" + ] + }, + "name": { + "constant_value": "crypto-key-example" + }, + "rotation_period": { + "constant_value": "31536002s" + } + }, + "schema_version": 1 + }, + { + "address": "google_kms_crypto_key.valid_key_1", + "mode": "managed", + "type": "google_kms_crypto_key", + "name": "valid_key_1", + "provider_config_key": "google", + "expressions": { + "key_ring": { + "references": [ + "google_kms_key_ring.keyring" + ] + }, + "name": { + "constant_value": "crypto-key-example" + }, + "rotation_period": { + "constant_value": "31536000s" + } + }, + "schema_version": 1 + }, + { + "address": "google_kms_key_ring.keyring", + "mode": "managed", + "type": "google_kms_key_ring", + "name": "keyring", + "provider_config_key": "google", + "expressions": { + "location": { + "constant_value": "global" + }, + "name": { + "constant_value": "keyring-example" + } + }, + "schema_version": 0 + } + ] + } + } +} From 14e15fd2950617b37e63223c9bbbe8a4e5edae46 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 3 May 2021 16:56:31 -0400 Subject: [PATCH 33/65] [RM-5352] Run regula rego tests with go test --- pkg/rego/builtin.go | 2 +- pkg/rego/load.go | 4 +- pkg/rego/rego_test.go | 98 +++++ pkg/rego/runrepl.go | 6 +- pkg/rego/runrules.go | 6 +- pkg/rego/runtest.go | 6 +- .../lib/fugue_regula_report_01_test.rego | 6 +- .../lib/fugue_regula_report_02_test.rego | 8 +- .../lib/fugue_regula_report_03_test.rego | 8 +- .../lib/fugue_regula_report_04_test.rego | 8 +- .../lib/fugue_resource_view_02_test.rego | 67 ++-- .../lib/fugue_resource_view_03_test.rego | 1 + .../lib/fugue_resource_view_modules_test.rego | 4 +- .../lib/inputs/invalid_encryption_infra.cfn | 49 +++ .../lib/inputs/invalid_encryption_infra.rego | 32 ++ rego/tests/lib/inputs/resource_view_01.tf | 41 --- .../lib/inputs/resource_view_01_infra.rego | 319 +---------------- .../lib/inputs/resource_view_01_infra.tfplan | 305 ++++++++++++++++ .../lib/inputs/resource_view_02_infra.rego | 329 +---------------- .../lib/inputs/resource_view_02_infra.tfplan | 319 +++++++++++++++++ rego/tests/lib/inputs/resource_view_03.tf | 41 --- .../lib/inputs/resource_view_03_infra.rego | 334 +----------------- .../lib/inputs/resource_view_03_infra.tfplan | 321 +++++++++++++++++ .../lib/inputs/resource_view_04_infra.rego | 129 +------ .../lib/inputs/resource_view_04_infra.tfplan | 115 ++++++ .../lib/inputs/valid_encryption_infra.cfn | 39 ++ .../lib/inputs/valid_encryption_infra.rego | 32 ++ .../lib/inputs/volume_encrypted_infra.rego | 32 ++ ...e_view_02.tf => volume_encrypted_infra.tf} | 34 +- .../lib/inputs/volume_encrypted_infra.tfplan | 222 ++++++++++++ 30 files changed, 1673 insertions(+), 1244 deletions(-) create mode 100644 pkg/rego/rego_test.go create mode 100644 rego/tests/lib/inputs/invalid_encryption_infra.cfn create mode 100644 rego/tests/lib/inputs/invalid_encryption_infra.rego delete mode 100644 rego/tests/lib/inputs/resource_view_01.tf create mode 100644 rego/tests/lib/inputs/resource_view_01_infra.tfplan create mode 100644 rego/tests/lib/inputs/resource_view_02_infra.tfplan delete mode 100644 rego/tests/lib/inputs/resource_view_03.tf create mode 100644 rego/tests/lib/inputs/resource_view_03_infra.tfplan create mode 100644 rego/tests/lib/inputs/resource_view_04_infra.tfplan create mode 100644 rego/tests/lib/inputs/valid_encryption_infra.cfn create mode 100644 rego/tests/lib/inputs/valid_encryption_infra.rego create mode 100644 rego/tests/lib/inputs/volume_encrypted_infra.rego rename rego/tests/lib/inputs/{resource_view_02.tf => volume_encrypted_infra.tf} (52%) create mode 100644 rego/tests/lib/inputs/volume_encrypted_infra.tfplan diff --git a/pkg/rego/builtin.go b/pkg/rego/builtin.go index 16ac85c6..a4d9dff6 100644 --- a/pkg/rego/builtin.go +++ b/pkg/rego/builtin.go @@ -80,7 +80,7 @@ func regulaLoadType(ctx rego.BuiltinContext, a *ast.Term, b *ast.Term) (*ast.Ter }) } -func registerBuiltins() { +func RegisterBuiltins() { rego.RegisterBuiltin1( ®o.Function{ Name: "regula_load", diff --git a/pkg/rego/load.go b/pkg/rego/load.go index acf77dba..bf6fa2d8 100644 --- a/pkg/rego/load.go +++ b/pkg/rego/load.go @@ -90,7 +90,7 @@ func loadDirectory(fsys fs.FS, path string, cb func(r RegoFile) error) error { return nil } -func loadOsFiles(paths []string, cb func(r RegoFile) error) error { +func LoadOSFiles(paths []string, cb func(r RegoFile) error) error { fsys := &osFs{} for _, path := range paths { info, err := os.Stat(path) @@ -115,7 +115,7 @@ func loadOsFiles(paths []string, cb func(r RegoFile) error) error { return nil } -func loadRegula(userOnly bool, cb func(r RegoFile) error) error { +func LoadRegula(userOnly bool, cb func(r RegoFile) error) error { if err := loadDirectory(regulaLib, "lib", cb); err != nil { return err } diff --git a/pkg/rego/rego_test.go b/pkg/rego/rego_test.go new file mode 100644 index 00000000..b2851519 --- /dev/null +++ b/pkg/rego/rego_test.go @@ -0,0 +1,98 @@ +package rego_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/fugue/regula/pkg/rego" + "github.com/open-policy-agent/opa/ast" + "github.com/open-policy-agent/opa/storage/inmem" + "github.com/open-policy-agent/opa/tester" + "github.com/stretchr/testify/assert" +) + +func formatFailedTest(r *tester.Result) string { + return fmt.Sprintf("%s.%s in file %s", r.Package, r.Name, r.Location.String()) +} + +func TestRegulaLib(t *testing.T) { + rego.RegisterBuiltins() + modules := map[string]*ast.Module{} + cb := func(r rego.RegoFile) error { + module, err := r.AstModule() + if err != nil { + return err + } + modules[r.Path()] = module + return nil + } + if err := rego.LoadRegula(true, cb); err != nil { + assert.Fail(t, "Failed to load regula library", err) + } + if err := rego.LoadOSFiles([]string{"../../rego/tests/lib"}, cb); err != nil { + assert.Fail(t, "Failed to load regula library tests", err) + } + ctx := context.Background() + ch, err := tester.NewRunner().SetStore(inmem.New()).Run(ctx, modules) + if err != nil { + assert.Fail(t, "Failed to run library tests through OPA", err) + } + failedTests := []string{} + hasFailures := false + errors := []error{} + for r := range ch { + if r.Fail { + hasFailures = true + failedTests = append(failedTests, formatFailedTest(r)) + } + hasFailures = hasFailures || r.Fail + if r.Error != nil { + errors = append(errors, r.Error) + } + } + + assert.Empty(t, errors) + assert.Falsef(t, hasFailures, "Some tests failed:\n%v", strings.Join(failedTests, "\n")) +} + +func TestRegulaRules(t *testing.T) { + rego.RegisterBuiltins() + modules := map[string]*ast.Module{} + cb := func(r rego.RegoFile) error { + module, err := r.AstModule() + if err != nil { + return err + } + modules[r.Path()] = module + return nil + } + if err := rego.LoadRegula(false, cb); err != nil { + assert.Fail(t, "Failed to load regula library", err) + } + if err := rego.LoadOSFiles([]string{"../../rego/tests/rules"}, cb); err != nil { + assert.Fail(t, "Failed to load regula library tests", err) + } + ctx := context.Background() + ch, err := tester.NewRunner().SetStore(inmem.New()).Run(ctx, modules) + if err != nil { + assert.Fail(t, "Failed to run library tests through OPA", err) + } + failedTests := []string{} + hasFailures := false + errors := []error{} + for r := range ch { + if r.Fail { + hasFailures = true + failedTests = append(failedTests, formatFailedTest(r)) + } + hasFailures = hasFailures || r.Fail + if r.Error != nil { + errors = append(errors, r.Error) + } + } + + assert.Empty(t, errors) + assert.Falsef(t, hasFailures, "Some tests failed:\n%v", strings.Join(failedTests, "\n")) +} diff --git a/pkg/rego/runrepl.go b/pkg/rego/runrepl.go index fe9cb133..dce45e5c 100644 --- a/pkg/rego/runrepl.go +++ b/pkg/rego/runrepl.go @@ -16,7 +16,7 @@ type RunREPLOptions struct { } func RunREPL(options *RunREPLOptions) error { - registerBuiltins() + RegisterBuiltins() store, err := initStore(options.Ctx, options.UserOnly, options.Includes) if err != nil { return err @@ -37,10 +37,10 @@ func initStore(ctx context.Context, userOnly bool, includes []string) (storage.S cb := func(r RegoFile) error { return store.UpsertPolicy(ctx, txn, r.Path(), r.Raw()) } - if err := loadRegula(userOnly, cb); err != nil { + if err := LoadRegula(userOnly, cb); err != nil { return nil, err } - if err := loadOsFiles(includes, cb); err != nil { + if err := LoadOSFiles(includes, cb); err != nil { return nil, err } if err := store.Commit(ctx, txn); err != nil { diff --git a/pkg/rego/runrules.go b/pkg/rego/runrules.go index f21f9c2a..6ff31964 100644 --- a/pkg/rego/runrules.go +++ b/pkg/rego/runrules.go @@ -32,7 +32,7 @@ type RunRulesOptions struct { // RunRules runs regula and user-specified rules on loaded inputs func RunRules(options *RunRulesOptions) (*rego.Result, error) { - registerBuiltins() + RegisterBuiltins() query, err := prepare(options.Ctx, options.UserOnly, options.Includes) if err != nil { return nil, err @@ -52,10 +52,10 @@ func prepare(ctx context.Context, userOnly bool, includes []string) (*rego.Prepa regoFuncs = append(regoFuncs, rego.Module(r.Path(), r.String())) return nil } - if err := loadRegula(userOnly, cb); err != nil { + if err := LoadRegula(userOnly, cb); err != nil { return nil, err } - if err := loadOsFiles(includes, cb); err != nil { + if err := LoadOSFiles(includes, cb); err != nil { return nil, err } query, err := rego.New(regoFuncs...).PrepareForEval(ctx) diff --git a/pkg/rego/runtest.go b/pkg/rego/runtest.go index 046d497d..38cf7241 100644 --- a/pkg/rego/runtest.go +++ b/pkg/rego/runtest.go @@ -15,7 +15,7 @@ type RunTestOptions struct { } func RunTest(options *RunTestOptions) error { - registerBuiltins() + RegisterBuiltins() store := inmem.New() modules := map[string]*ast.Module{} cb := func(r RegoFile) error { @@ -26,10 +26,10 @@ func RunTest(options *RunTestOptions) error { modules[r.Path()] = module return nil } - if err := loadRegula(true, cb); err != nil { + if err := LoadRegula(true, cb); err != nil { return err } - if err := loadOsFiles(options.Includes, cb); err != nil { + if err := LoadOSFiles(options.Includes, cb); err != nil { return err } ch, err := tester.NewRunner().SetStore(store).EnableTracing(true).Run(options.Ctx, modules) diff --git a/rego/tests/lib/fugue_regula_report_01_test.rego b/rego/tests/lib/fugue_regula_report_01_test.rego index 23715ca1..b93e2827 100644 --- a/rego/tests/lib/fugue_regula_report_01_test.rego +++ b/rego/tests/lib/fugue_regula_report_01_test.rego @@ -17,10 +17,10 @@ package fugue.regula_report_01_test import data.fugue.regula -import data.tests.rules.tf.aws.ebs.inputs.volume_encrypted_infra +import data.tests.lib.inputs.volume_encrypted_infra # We reuse the mock input from another test case. -mock_plan_input = volume_encrypted_infra.mock_plan_input +mock_config = volume_encrypted_infra.mock_config[0].content # We construct some mock rules as well. mock_rules = { @@ -47,7 +47,7 @@ mock_rules = { # Produce a report. report = ret { - ret = regula.report with input as mock_plan_input with data.rules as mock_rules + ret = regula.report with input as mock_config with data.rules as mock_rules } contains_result(arr, result) { diff --git a/rego/tests/lib/fugue_regula_report_02_test.rego b/rego/tests/lib/fugue_regula_report_02_test.rego index d9f24622..396a982e 100644 --- a/rego/tests/lib/fugue_regula_report_02_test.rego +++ b/rego/tests/lib/fugue_regula_report_02_test.rego @@ -16,17 +16,17 @@ package fugue.regula_report_02_test import data.fugue.regula -import data.tests.rules.cfn.cloudtrail.inputs.invalid_encryption_infra as input1 -import data.tests.rules.cfn.s3.inputs.valid_encryption_infra as input2 +import data.tests.lib.inputs.invalid_encryption_infra as input1 +import data.tests.lib.inputs.valid_encryption_infra as input2 mock_input := [ { "filepath": "template1.yaml", - "content": input1.mock_plan_input + "content": input1.mock_config[0].content }, { "filepath": "template2.yaml", - "content": input2.mock_plan_input + "content": input2.mock_config[0].content } ] diff --git a/rego/tests/lib/fugue_regula_report_03_test.rego b/rego/tests/lib/fugue_regula_report_03_test.rego index 2d49b624..a22933d9 100644 --- a/rego/tests/lib/fugue_regula_report_03_test.rego +++ b/rego/tests/lib/fugue_regula_report_03_test.rego @@ -16,17 +16,17 @@ package fugue.regula_report_03_test import data.fugue.regula -import data.tests.rules.cfn.cloudtrail.inputs.invalid_encryption_infra as input1 -import data.tests.rules.cfn.s3.inputs.valid_encryption_infra as input2 +import data.tests.lib.inputs.invalid_encryption_infra as input1 +import data.tests.lib.inputs.valid_encryption_infra as input2 mock_input := [ { "filepath": "template1.yaml", - "content": input1.mock_plan_input + "content": input1.mock_config[0].content }, { "filepath": "template2.yaml", - "content": input2.mock_plan_input + "content": input2.mock_config[0].content } ] diff --git a/rego/tests/lib/fugue_regula_report_04_test.rego b/rego/tests/lib/fugue_regula_report_04_test.rego index c20ea581..0166132f 100644 --- a/rego/tests/lib/fugue_regula_report_04_test.rego +++ b/rego/tests/lib/fugue_regula_report_04_test.rego @@ -16,17 +16,17 @@ package fugue.regula_report_04_test import data.fugue.regula -import data.tests.rules.cfn.cloudtrail.inputs.invalid_encryption_infra as input1 -import data.tests.rules.cfn.s3.inputs.valid_encryption_infra as input2 +import data.tests.lib.inputs.invalid_encryption_infra as input1 +import data.tests.lib.inputs.valid_encryption_infra as input2 mock_input := [ { "filepath": "template1.yaml", - "content": input1.mock_plan_input + "content": input1.mock_config[0].content }, { "filepath": "template2.yaml", - "content": input2.mock_plan_input + "content": input2.mock_config[0].content } ] diff --git a/rego/tests/lib/fugue_resource_view_02_test.rego b/rego/tests/lib/fugue_resource_view_02_test.rego index 00614b11..26493b28 100644 --- a/rego/tests/lib/fugue_resource_view_02_test.rego +++ b/rego/tests/lib/fugue_resource_view_02_test.rego @@ -18,59 +18,60 @@ import data.tests.lib.inputs.resource_view_02_infra test_resource_view_02 { resource_view_02_infra.mock_resources == { "aws_s3_bucket.example": { - "id": "aws_s3_bucket.example", + "_provider": "aws", + "_type": "aws_s3_bucket", "acl": "private", - "website": [], - "replication_configuration": [], - "cors_rule": [], - "tags": null, "bucket_prefix": "example", - "policy": null, - "server_side_encryption_configuration": [], + "cors_rule": [], + "force_destroy": false, "grant": [], - "object_lock_configuration": [], - "logging": [], + "id": "aws_s3_bucket.example", "lifecycle_rule": [], - "_type": "aws_s3_bucket", - "_provider": "aws", - "force_destroy": false + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [], }, "data.aws_iam_policy_document.example": { + "_provider": "aws", + "_type": "aws_iam_policy_document", "id": "data.aws_iam_policy_document.example", + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, "statement": [{ "actions": ["s3:*"], - "sid": null, - "not_resources": null, - "resources": [ - "arn:aws:s3:::some-example-bucket/*", - "aws_s3_bucket.example" - ], - "effect": "Allow", "condition": [], + "effect": "Allow", + "not_actions": null, "not_principals": [], + "not_resources": null, "principals": [{ "type": "*", - "identifiers": ["*"] + "identifiers": ["*"], }], - "not_actions": null + "resources": [ + "arn:aws:s3:::some-example-bucket/*", + "aws_s3_bucket.example", + ], + "sid": null, }], - "override_json": null, - "override_policy_documents": null, - "source_policy_documents": null, - "source_json": null, "version": null, - "policy_id": null, - "_type": "aws_iam_policy_document", - "_provider": "aws", }, "aws_iam_policy.example": { - "id": "aws_iam_policy.example", + "_provider": "aws", + "_type": "aws_iam_policy", "description": null, - "policy": "data.aws_iam_policy_document.example", + "id": "aws_iam_policy.example", + "name_prefix": null, "path": "/", - "_type": "aws_iam_policy", - "_provider": "aws", - "name_prefix": null + "policy": "data.aws_iam_policy_document.example", + "tags": null, } } } diff --git a/rego/tests/lib/fugue_resource_view_03_test.rego b/rego/tests/lib/fugue_resource_view_03_test.rego index 6841db41..618342ad 100644 --- a/rego/tests/lib/fugue_resource_view_03_test.rego +++ b/rego/tests/lib/fugue_resource_view_03_test.rego @@ -64,6 +64,7 @@ test_resource_view_03 { "location": "westeurope", "min_tls_version": "TLS1_0", "name": "main", + "nfsv3_enabled": false, "resource_group_name": "main", "static_website": [], "tags": null, diff --git a/rego/tests/lib/fugue_resource_view_modules_test.rego b/rego/tests/lib/fugue_resource_view_modules_test.rego index d035f7a5..58aa3f6e 100644 --- a/rego/tests/lib/fugue_resource_view_modules_test.rego +++ b/rego/tests/lib/fugue_resource_view_modules_test.rego @@ -24,10 +24,10 @@ test_mock_resource_view { } mock_resource_view = ret { - ret = resource_view.resource_view with input as mock_plan_input + ret = resource_view.resource_view with input as mock_config } -mock_plan_input = { +mock_config = { "format_version": "0.1", "terraform_version": "0.12.18", "variables": { diff --git a/rego/tests/lib/inputs/invalid_encryption_infra.cfn b/rego/tests/lib/inputs/invalid_encryption_infra.cfn new file mode 100644 index 00000000..3d5da4c2 --- /dev/null +++ b/rego/tests/lib/inputs/invalid_encryption_infra.cfn @@ -0,0 +1,49 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +AWSTemplateFormatVersion: "2010-09-09" +Description: Invalid CloudTrail encryption configuration +Resources: + CloudTrailLogging: + Type: AWS::CloudTrail::Trail + Properties: + IncludeGlobalServiceEvents: false + IsLogging: true + S3BucketName: !Ref LoggingBucket + S3KeyPrefix: prefix + TrailName: cf-fuguetest-trail + LoggingBucket: + Type: AWS::S3::Bucket + LoggingBucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: !Ref LoggingBucket + PolicyDocument: + Statement: + - Sid: AWSCloudTrailAclCheck + Effect: Allow + Principal: + Service: cloudtrail.amazonaws.com + Action: s3:GetBucketAcl + Resource: + - !GetAtt LoggingBucket.Arn + - Sid: AWSCloudTrailWrite + Effect: Allow + Principal: + Service: cloudtrail.amazonaws.com + Action: s3:PutObject + Resource: + - !Sub "${LoggingBucket.Arn}/*" + Condition: + StringEquals: + "s3:x-amz-acl": "bucket-owner-full-control" diff --git a/rego/tests/lib/inputs/invalid_encryption_infra.rego b/rego/tests/lib/inputs/invalid_encryption_infra.rego new file mode 100644 index 00000000..4bda1862 --- /dev/null +++ b/rego/tests/lib/inputs/invalid_encryption_infra.rego @@ -0,0 +1,32 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This package was automatically generated from: +# +# tests/lib/inputs/invalid_encryption_infra.cfn +# +# using 'generate_test_inputs.sh' and should not be modified +# directly. +# +# It provides three inputs for testing: +# - mock_input: The resource view input as passed to advanced rules +# - mock_resources: The resources present as a convenience for tests +# - mock_config: The raw config input as its parsed by regula +package tests.lib.inputs.invalid_encryption_infra + +import data.regula + +mock_config := regula_load_type("invalid_encryption_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_01.tf b/rego/tests/lib/inputs/resource_view_01.tf deleted file mode 100644 index 131a3e9f..00000000 --- a/rego/tests/lib/inputs/resource_view_01.tf +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2020-2021 Fugue, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -provider "aws" { - region = "us-west-2" -} - -resource "aws_s3_bucket" "example" { - bucket_prefix = "example" -} - -data "aws_iam_policy_document" "example" { - statement { - effect = "Allow" - actions = ["s3:*"] - - principals { - type = "*" - identifiers = ["*"] - } - - resources = [ - "arn:aws:s3:::${aws_s3_bucket.example.id}/*", - ] - } -} - -resource "aws_s3_bucket_policy" "example" { - bucket = "${aws_s3_bucket.example.id}" - policy = "${data.aws_iam_policy_document.example.json}" -} diff --git a/rego/tests/lib/inputs/resource_view_01_infra.rego b/rego/tests/lib/inputs/resource_view_01_infra.rego index 14626f76..7ef431f1 100644 --- a/rego/tests/lib/inputs/resource_view_01_infra.rego +++ b/rego/tests/lib/inputs/resource_view_01_infra.rego @@ -16,320 +16,17 @@ # # tests/lib/inputs/resource_view_01_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_01_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.29", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": "example", - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - } - }, - { - "address": "aws_s3_bucket_policy.example", - "mode": "managed", - "type": "aws_s3_bucket_policy", - "name": "example", - "provider_name": "aws", - "schema_version": 0 - }, - { - "address": "data.aws_iam_policy_document.example", - "mode": "data", - "type": "aws_iam_policy_document", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "override_json": null, - "override_policy_documents": null, - "policy_id": null, - "source_json": null, - "source_policy_documents": null, - "statement": [ - { - "actions": [ - "s3:*" - ], - "condition": [], - "effect": "Allow", - "not_actions": null, - "not_principals": [], - "not_resources": null, - "principals": [ - { - "identifiers": [ - "*" - ], - "type": "*" - } - ], - "resources": [], - "sid": null - } - ], - "version": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": "example", - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - }, - { - "address": "aws_s3_bucket_policy.example", - "mode": "managed", - "type": "aws_s3_bucket_policy", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": {}, - "after_unknown": { - "bucket": true, - "id": true, - "policy": true - } - } - }, - { - "address": "data.aws_iam_policy_document.example", - "mode": "data", - "type": "aws_iam_policy_document", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "read" - ], - "before": null, - "after": { - "override_json": null, - "override_policy_documents": null, - "policy_id": null, - "source_json": null, - "source_policy_documents": null, - "statement": [ - { - "actions": [ - "s3:*" - ], - "condition": [], - "effect": "Allow", - "not_actions": null, - "not_principals": [], - "not_resources": null, - "principals": [ - { - "identifiers": [ - "*" - ], - "type": "*" - } - ], - "resources": [], - "sid": null - } - ], - "version": null - }, - "after_unknown": { - "id": true, - "json": true, - "statement": [ - { - "actions": [ - false - ], - "condition": [], - "not_principals": [], - "principals": [ - { - "identifiers": [ - false - ] - } - ], - "resources": [ - true - ] - } - ] - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-west-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "bucket_prefix": { - "constant_value": "example" - } - }, - "schema_version": 0 - }, - { - "address": "aws_s3_bucket_policy.example", - "mode": "managed", - "type": "aws_s3_bucket_policy", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "bucket": { - "references": [ - "aws_s3_bucket.example" - ] - }, - "policy": { - "references": [ - "data.aws_iam_policy_document.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "data.aws_iam_policy_document.example", - "mode": "data", - "type": "aws_iam_policy_document", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "statement": [ - { - "actions": { - "constant_value": [ - "s3:*" - ] - }, - "effect": { - "constant_value": "Allow" - }, - "principals": [ - { - "identifiers": { - "constant_value": [ - "*" - ] - }, - "type": { - "constant_value": "*" - } - } - ], - "resources": { - "references": [ - "aws_s3_bucket.example" - ] - } - } - ] - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("resource_view_01_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_01_infra.tfplan b/rego/tests/lib/inputs/resource_view_01_infra.tfplan new file mode 100644 index 00000000..42d040bd --- /dev/null +++ b/rego/tests/lib/inputs/resource_view_01_infra.tfplan @@ -0,0 +1,305 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": "example", + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + } + }, + { + "address": "aws_s3_bucket_policy.example", + "mode": "managed", + "type": "aws_s3_bucket_policy", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0 + }, + { + "address": "data.aws_iam_policy_document.example", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, + "statement": [ + { + "actions": [ + "s3:*" + ], + "condition": [], + "effect": "Allow", + "not_actions": null, + "not_principals": [], + "not_resources": null, + "principals": [ + { + "identifiers": [ + "*" + ], + "type": "*" + } + ], + "resources": [], + "sid": null + } + ], + "version": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": "example", + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + }, + { + "address": "aws_s3_bucket_policy.example", + "mode": "managed", + "type": "aws_s3_bucket_policy", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": {}, + "after_unknown": { + "bucket": true, + "id": true, + "policy": true + } + } + }, + { + "address": "data.aws_iam_policy_document.example", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "read" + ], + "before": null, + "after": { + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, + "statement": [ + { + "actions": [ + "s3:*" + ], + "condition": [], + "effect": "Allow", + "not_actions": null, + "not_principals": [], + "not_resources": null, + "principals": [ + { + "identifiers": [ + "*" + ], + "type": "*" + } + ], + "resources": [], + "sid": null + } + ], + "version": null + }, + "after_unknown": { + "id": true, + "json": true, + "statement": [ + { + "actions": [ + false + ], + "condition": [], + "not_principals": [], + "principals": [ + { + "identifiers": [ + false + ] + } + ], + "resources": [ + true + ] + } + ] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "bucket_prefix": { + "constant_value": "example" + } + }, + "schema_version": 0 + }, + { + "address": "aws_s3_bucket_policy.example", + "mode": "managed", + "type": "aws_s3_bucket_policy", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "bucket": { + "references": [ + "aws_s3_bucket.example" + ] + }, + "policy": { + "references": [ + "data.aws_iam_policy_document.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "data.aws_iam_policy_document.example", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "statement": [ + { + "actions": { + "constant_value": [ + "s3:*" + ] + }, + "effect": { + "constant_value": "Allow" + }, + "principals": [ + { + "identifiers": { + "constant_value": [ + "*" + ] + }, + "type": { + "constant_value": "*" + } + } + ], + "resources": { + "references": [ + "aws_s3_bucket.example" + ] + } + } + ] + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/lib/inputs/resource_view_02_infra.rego b/rego/tests/lib/inputs/resource_view_02_infra.rego index 4c8f5049..c4c75c53 100644 --- a/rego/tests/lib/inputs/resource_view_02_infra.rego +++ b/rego/tests/lib/inputs/resource_view_02_infra.rego @@ -16,330 +16,17 @@ # # tests/lib/inputs/resource_view_02_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_02_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.29", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_iam_policy.example", - "mode": "managed", - "type": "aws_iam_policy", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "description": null, - "name_prefix": null, - "path": "/" - } - }, - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": "example", - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - } - }, - { - "address": "data.aws_iam_policy_document.example", - "mode": "data", - "type": "aws_iam_policy_document", - "name": "example", - "provider_name": "aws", - "schema_version": 0, - "values": { - "override_json": null, - "override_policy_documents": null, - "policy_id": null, - "source_json": null, - "source_policy_documents": null, - "statement": [ - { - "actions": [ - "s3:*" - ], - "condition": [], - "effect": "Allow", - "not_actions": null, - "not_principals": [], - "not_resources": null, - "principals": [ - { - "identifiers": [ - "*" - ], - "type": "*" - } - ], - "resources": [ - "arn:aws:s3:::some-example-bucket/*" - ], - "sid": null - } - ], - "version": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_iam_policy.example", - "mode": "managed", - "type": "aws_iam_policy", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "description": null, - "name_prefix": null, - "path": "/" - }, - "after_unknown": { - "arn": true, - "id": true, - "name": true, - "policy": true - } - } - }, - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": "example", - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - }, - { - "address": "data.aws_iam_policy_document.example", - "mode": "data", - "type": "aws_iam_policy_document", - "name": "example", - "provider_name": "aws", - "change": { - "actions": [ - "read" - ], - "before": null, - "after": { - "override_json": null, - "override_policy_documents": null, - "policy_id": null, - "source_json": null, - "source_policy_documents": null, - "statement": [ - { - "actions": [ - "s3:*" - ], - "condition": [], - "effect": "Allow", - "not_actions": null, - "not_principals": [], - "not_resources": null, - "principals": [ - { - "identifiers": [ - "*" - ], - "type": "*" - } - ], - "resources": [ - "arn:aws:s3:::some-example-bucket/*" - ], - "sid": null - } - ], - "version": null - }, - "after_unknown": { - "id": true, - "json": true, - "statement": [ - { - "actions": [ - false - ], - "condition": [], - "not_principals": [], - "principals": [ - { - "identifiers": [ - false - ] - } - ], - "resources": [ - false, - true - ] - } - ] - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-west-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_iam_policy.example", - "mode": "managed", - "type": "aws_iam_policy", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "policy": { - "references": [ - "data.aws_iam_policy_document.example" - ] - } - }, - "schema_version": 0 - }, - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "bucket_prefix": { - "constant_value": "example" - } - }, - "schema_version": 0 - }, - { - "address": "data.aws_iam_policy_document.example", - "mode": "data", - "type": "aws_iam_policy_document", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "statement": [ - { - "actions": { - "constant_value": [ - "s3:*" - ] - }, - "effect": { - "constant_value": "Allow" - }, - "principals": [ - { - "identifiers": { - "constant_value": [ - "*" - ] - }, - "type": { - "constant_value": "*" - } - } - ], - "resources": { - "references": [ - "aws_s3_bucket.example" - ] - } - } - ] - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("resource_view_02_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_02_infra.tfplan b/rego/tests/lib/inputs/resource_view_02_infra.tfplan new file mode 100644 index 00000000..d6bff0ef --- /dev/null +++ b/rego/tests/lib/inputs/resource_view_02_infra.tfplan @@ -0,0 +1,319 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_policy.example", + "mode": "managed", + "type": "aws_iam_policy", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "description": null, + "name_prefix": null, + "path": "/", + "tags": null + } + }, + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": "example", + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + } + }, + { + "address": "data.aws_iam_policy_document.example", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, + "statement": [ + { + "actions": [ + "s3:*" + ], + "condition": [], + "effect": "Allow", + "not_actions": null, + "not_principals": [], + "not_resources": null, + "principals": [ + { + "identifiers": [ + "*" + ], + "type": "*" + } + ], + "resources": [ + "arn:aws:s3:::some-example-bucket/*" + ], + "sid": null + } + ], + "version": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_policy.example", + "mode": "managed", + "type": "aws_iam_policy", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "name_prefix": null, + "path": "/", + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "name": true, + "policy": true, + "policy_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": "example", + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + }, + { + "address": "data.aws_iam_policy_document.example", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "read" + ], + "before": null, + "after": { + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, + "statement": [ + { + "actions": [ + "s3:*" + ], + "condition": [], + "effect": "Allow", + "not_actions": null, + "not_principals": [], + "not_resources": null, + "principals": [ + { + "identifiers": [ + "*" + ], + "type": "*" + } + ], + "resources": [ + "arn:aws:s3:::some-example-bucket/*" + ], + "sid": null + } + ], + "version": null + }, + "after_unknown": { + "id": true, + "json": true, + "statement": [ + { + "actions": [ + false + ], + "condition": [], + "not_principals": [], + "principals": [ + { + "identifiers": [ + false + ] + } + ], + "resources": [ + false, + true + ] + } + ] + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_policy.example", + "mode": "managed", + "type": "aws_iam_policy", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "policy": { + "references": [ + "data.aws_iam_policy_document.example" + ] + } + }, + "schema_version": 0 + }, + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "bucket_prefix": { + "constant_value": "example" + } + }, + "schema_version": 0 + }, + { + "address": "data.aws_iam_policy_document.example", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "statement": [ + { + "actions": { + "constant_value": [ + "s3:*" + ] + }, + "effect": { + "constant_value": "Allow" + }, + "principals": [ + { + "identifiers": { + "constant_value": [ + "*" + ] + }, + "type": { + "constant_value": "*" + } + } + ], + "resources": { + "references": [ + "aws_s3_bucket.example" + ] + } + } + ] + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/lib/inputs/resource_view_03.tf b/rego/tests/lib/inputs/resource_view_03.tf deleted file mode 100644 index 92286caf..00000000 --- a/rego/tests/lib/inputs/resource_view_03.tf +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2020-2021 Fugue, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -provider "azurerm" { - features { - } -} - -resource "azurerm_resource_group" "main" { - name = "main" - location = "West Europe" -} - -resource "azurerm_storage_account" "main" { - name = "main" - resource_group_name = "${azurerm_resource_group.main.name}" - location = "${azurerm_resource_group.main.location}" - account_tier = "Standard" - account_replication_type = "GRS" -} - -resource "azurerm_monitor_log_profile" "main" { - name = "main" - categories = ["Action", "Delete", "Write"] - locations = ["global", "${azurerm_resource_group.main.location}"] - storage_account_id = "${azurerm_storage_account.main.id}" - - retention_policy { - enabled = false - } -} diff --git a/rego/tests/lib/inputs/resource_view_03_infra.rego b/rego/tests/lib/inputs/resource_view_03_infra.rego index 3bea205f..b28a399b 100644 --- a/rego/tests/lib/inputs/resource_view_03_infra.rego +++ b/rego/tests/lib/inputs/resource_view_03_infra.rego @@ -16,335 +16,17 @@ # # tests/lib/inputs/resource_view_03_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_03_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.29", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "azurerm_monitor_log_profile.main", - "mode": "managed", - "type": "azurerm_monitor_log_profile", - "name": "main", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "categories": [ - "Action", - "Delete", - "Write" - ], - "locations": [ - "global", - "westeurope" - ], - "name": "main", - "retention_policy": [ - { - "days": 0, - "enabled": false - } - ], - "servicebus_rule_id": null, - "timeouts": null - } - }, - { - "address": "azurerm_resource_group.main", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "main", - "provider_name": "azurerm", - "schema_version": 0, - "values": { - "location": "westeurope", - "name": "main", - "tags": null, - "timeouts": null - } - }, - { - "address": "azurerm_storage_account.main", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "main", - "provider_name": "azurerm", - "schema_version": 2, - "values": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "main", - "resource_group_name": "main", - "static_website": [], - "tags": null, - "timeouts": null - } - } - ] - } - }, - "resource_changes": [ - { - "address": "azurerm_monitor_log_profile.main", - "mode": "managed", - "type": "azurerm_monitor_log_profile", - "name": "main", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "categories": [ - "Action", - "Delete", - "Write" - ], - "locations": [ - "global", - "westeurope" - ], - "name": "main", - "retention_policy": [ - { - "days": 0, - "enabled": false - } - ], - "servicebus_rule_id": null, - "timeouts": null - }, - "after_unknown": { - "categories": [ - false, - false, - false - ], - "id": true, - "locations": [ - false, - false - ], - "retention_policy": [ - {} - ], - "storage_account_id": true - } - } - }, - { - "address": "azurerm_resource_group.main", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "main", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "location": "westeurope", - "name": "main", - "tags": null, - "timeouts": null - }, - "after_unknown": { - "id": true - } - } - }, - { - "address": "azurerm_storage_account.main", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "main", - "provider_name": "azurerm", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "account_kind": "StorageV2", - "account_replication_type": "GRS", - "account_tier": "Standard", - "allow_blob_public_access": false, - "custom_domain": [], - "enable_https_traffic_only": true, - "is_hns_enabled": false, - "location": "westeurope", - "min_tls_version": "TLS1_0", - "name": "main", - "resource_group_name": "main", - "static_website": [], - "tags": null, - "timeouts": null - }, - "after_unknown": { - "access_tier": true, - "blob_properties": true, - "custom_domain": [], - "id": true, - "identity": true, - "large_file_share_enabled": true, - "network_rules": true, - "primary_access_key": true, - "primary_blob_connection_string": true, - "primary_blob_endpoint": true, - "primary_blob_host": true, - "primary_connection_string": true, - "primary_dfs_endpoint": true, - "primary_dfs_host": true, - "primary_file_endpoint": true, - "primary_file_host": true, - "primary_location": true, - "primary_queue_endpoint": true, - "primary_queue_host": true, - "primary_table_endpoint": true, - "primary_table_host": true, - "primary_web_endpoint": true, - "primary_web_host": true, - "queue_properties": true, - "secondary_access_key": true, - "secondary_blob_connection_string": true, - "secondary_blob_endpoint": true, - "secondary_blob_host": true, - "secondary_connection_string": true, - "secondary_dfs_endpoint": true, - "secondary_dfs_host": true, - "secondary_file_endpoint": true, - "secondary_file_host": true, - "secondary_location": true, - "secondary_queue_endpoint": true, - "secondary_queue_host": true, - "secondary_table_endpoint": true, - "secondary_table_host": true, - "secondary_web_endpoint": true, - "secondary_web_host": true, - "static_website": [] - } - } - } - ], - "configuration": { - "provider_config": { - "azurerm": { - "name": "azurerm", - "expressions": { - "features": [ - {} - ] - } - } - }, - "root_module": { - "resources": [ - { - "address": "azurerm_monitor_log_profile.main", - "mode": "managed", - "type": "azurerm_monitor_log_profile", - "name": "main", - "provider_config_key": "azurerm", - "expressions": { - "categories": { - "constant_value": [ - "Action", - "Delete", - "Write" - ] - }, - "locations": { - "references": [ - "azurerm_resource_group.main" - ] - }, - "name": { - "constant_value": "main" - }, - "retention_policy": [ - { - "enabled": { - "constant_value": false - } - } - ], - "storage_account_id": { - "references": [ - "azurerm_storage_account.main" - ] - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_resource_group.main", - "mode": "managed", - "type": "azurerm_resource_group", - "name": "main", - "provider_config_key": "azurerm", - "expressions": { - "location": { - "constant_value": "West Europe" - }, - "name": { - "constant_value": "main" - } - }, - "schema_version": 0 - }, - { - "address": "azurerm_storage_account.main", - "mode": "managed", - "type": "azurerm_storage_account", - "name": "main", - "provider_config_key": "azurerm", - "expressions": { - "account_replication_type": { - "constant_value": "GRS" - }, - "account_tier": { - "constant_value": "Standard" - }, - "location": { - "references": [ - "azurerm_resource_group.main" - ] - }, - "name": { - "constant_value": "main" - }, - "resource_group_name": { - "references": [ - "azurerm_resource_group.main" - ] - } - }, - "schema_version": 2 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("resource_view_03_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_03_infra.tfplan b/rego/tests/lib/inputs/resource_view_03_infra.tfplan new file mode 100644 index 00000000..b3336605 --- /dev/null +++ b/rego/tests/lib/inputs/resource_view_03_infra.tfplan @@ -0,0 +1,321 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "azurerm_monitor_log_profile.main", + "mode": "managed", + "type": "azurerm_monitor_log_profile", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "categories": [ + "Action", + "Delete", + "Write" + ], + "locations": [ + "global", + "westeurope" + ], + "name": "main", + "retention_policy": [ + { + "days": 0, + "enabled": false + } + ], + "servicebus_rule_id": null, + "timeouts": null + } + }, + { + "address": "azurerm_resource_group.main", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 0, + "values": { + "location": "westeurope", + "name": "main", + "tags": null, + "timeouts": null + } + }, + { + "address": "azurerm_storage_account.main", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "schema_version": 2, + "values": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "main", + "nfsv3_enabled": false, + "resource_group_name": "main", + "static_website": [], + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "azurerm_monitor_log_profile.main", + "mode": "managed", + "type": "azurerm_monitor_log_profile", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "categories": [ + "Action", + "Delete", + "Write" + ], + "locations": [ + "global", + "westeurope" + ], + "name": "main", + "retention_policy": [ + { + "days": 0, + "enabled": false + } + ], + "servicebus_rule_id": null, + "timeouts": null + }, + "after_unknown": { + "categories": [ + false, + false, + false + ], + "id": true, + "locations": [ + false, + false + ], + "retention_policy": [ + {} + ], + "storage_account_id": true + } + } + }, + { + "address": "azurerm_resource_group.main", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "location": "westeurope", + "name": "main", + "tags": null, + "timeouts": null + }, + "after_unknown": { + "id": true + } + } + }, + { + "address": "azurerm_storage_account.main", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "main", + "provider_name": "registry.terraform.io/hashicorp/azurerm", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_kind": "StorageV2", + "account_replication_type": "GRS", + "account_tier": "Standard", + "allow_blob_public_access": false, + "custom_domain": [], + "enable_https_traffic_only": true, + "is_hns_enabled": false, + "location": "westeurope", + "min_tls_version": "TLS1_0", + "name": "main", + "nfsv3_enabled": false, + "resource_group_name": "main", + "static_website": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "access_tier": true, + "blob_properties": true, + "custom_domain": [], + "id": true, + "identity": true, + "large_file_share_enabled": true, + "network_rules": true, + "primary_access_key": true, + "primary_blob_connection_string": true, + "primary_blob_endpoint": true, + "primary_blob_host": true, + "primary_connection_string": true, + "primary_dfs_endpoint": true, + "primary_dfs_host": true, + "primary_file_endpoint": true, + "primary_file_host": true, + "primary_location": true, + "primary_queue_endpoint": true, + "primary_queue_host": true, + "primary_table_endpoint": true, + "primary_table_host": true, + "primary_web_endpoint": true, + "primary_web_host": true, + "queue_properties": true, + "secondary_access_key": true, + "secondary_blob_connection_string": true, + "secondary_blob_endpoint": true, + "secondary_blob_host": true, + "secondary_connection_string": true, + "secondary_dfs_endpoint": true, + "secondary_dfs_host": true, + "secondary_file_endpoint": true, + "secondary_file_host": true, + "secondary_location": true, + "secondary_queue_endpoint": true, + "secondary_queue_host": true, + "secondary_table_endpoint": true, + "secondary_table_host": true, + "secondary_web_endpoint": true, + "secondary_web_host": true, + "static_website": [] + } + } + } + ], + "configuration": { + "provider_config": { + "azurerm": { + "name": "azurerm", + "expressions": { + "features": [ + {} + ] + } + } + }, + "root_module": { + "resources": [ + { + "address": "azurerm_monitor_log_profile.main", + "mode": "managed", + "type": "azurerm_monitor_log_profile", + "name": "main", + "provider_config_key": "azurerm", + "expressions": { + "categories": { + "constant_value": [ + "Action", + "Delete", + "Write" + ] + }, + "locations": { + "references": [ + "azurerm_resource_group.main" + ] + }, + "name": { + "constant_value": "main" + }, + "retention_policy": [ + { + "enabled": { + "constant_value": false + } + } + ], + "storage_account_id": { + "references": [ + "azurerm_storage_account.main" + ] + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_resource_group.main", + "mode": "managed", + "type": "azurerm_resource_group", + "name": "main", + "provider_config_key": "azurerm", + "expressions": { + "location": { + "constant_value": "West Europe" + }, + "name": { + "constant_value": "main" + } + }, + "schema_version": 0 + }, + { + "address": "azurerm_storage_account.main", + "mode": "managed", + "type": "azurerm_storage_account", + "name": "main", + "provider_config_key": "azurerm", + "expressions": { + "account_replication_type": { + "constant_value": "GRS" + }, + "account_tier": { + "constant_value": "Standard" + }, + "location": { + "references": [ + "azurerm_resource_group.main" + ] + }, + "name": { + "constant_value": "main" + }, + "resource_group_name": { + "references": [ + "azurerm_resource_group.main" + ] + } + }, + "schema_version": 2 + } + ] + } + } +} diff --git a/rego/tests/lib/inputs/resource_view_04_infra.rego b/rego/tests/lib/inputs/resource_view_04_infra.rego index 94d30313..f973caf5 100644 --- a/rego/tests/lib/inputs/resource_view_04_infra.rego +++ b/rego/tests/lib/inputs/resource_view_04_infra.rego @@ -16,130 +16,17 @@ # # tests/lib/inputs/resource_view_04_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_04_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.13.5", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_name": "registry.terraform.io/hashicorp/aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket_prefix": "example", - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_name": "registry.terraform.io/hashicorp/aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket_prefix": "example", - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": null, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [], - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-west-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_s3_bucket.example", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "example", - "provider_config_key": "aws", - "expressions": { - "bucket_prefix": { - "constant_value": "example" - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("resource_view_04_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_04_infra.tfplan b/rego/tests/lib/inputs/resource_view_04_infra.tfplan new file mode 100644 index 00000000..9629ada8 --- /dev/null +++ b/rego/tests/lib/inputs/resource_view_04_infra.tfplan @@ -0,0 +1,115 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket_prefix": "example", + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket_prefix": "example", + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": null, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [], + "tags_all": true, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.example", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "example", + "provider_config_key": "aws", + "expressions": { + "bucket_prefix": { + "constant_value": "example" + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/lib/inputs/valid_encryption_infra.cfn b/rego/tests/lib/inputs/valid_encryption_infra.cfn new file mode 100644 index 00000000..1c68351b --- /dev/null +++ b/rego/tests/lib/inputs/valid_encryption_infra.cfn @@ -0,0 +1,39 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +AWSTemplateFormatVersion: "2010-09-09" +Description: Valid S3 encryption configuration +Resources: + KMSKey: + Type: AWS::KMS::Key + Properties: + Description: This key is used to encrypt bucket objects + KeyPolicy: + Version: "2012-10-17" + Id: "default-key-policy" + Statement: + - Sid: Enable IAM User Permissions + Effect: Allow + Principal: + AWS: !Sub "arn:aws:iam::${AWS::AccountId}:root" + Action: "kms:*" + Resource: "*" + PendingWindowInDays: 10 + Bucket: + Type: AWS::S3::Bucket + Properties: + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + KMSMasterKeyID: !Ref KMSKey + SSEAlgorithm: aws:kms diff --git a/rego/tests/lib/inputs/valid_encryption_infra.rego b/rego/tests/lib/inputs/valid_encryption_infra.rego new file mode 100644 index 00000000..8500787c --- /dev/null +++ b/rego/tests/lib/inputs/valid_encryption_infra.rego @@ -0,0 +1,32 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This package was automatically generated from: +# +# tests/lib/inputs/valid_encryption_infra.cfn +# +# using 'generate_test_inputs.sh' and should not be modified +# directly. +# +# It provides three inputs for testing: +# - mock_input: The resource view input as passed to advanced rules +# - mock_resources: The resources present as a convenience for tests +# - mock_config: The raw config input as its parsed by regula +package tests.lib.inputs.valid_encryption_infra + +import data.regula + +mock_config := regula_load_type("valid_encryption_infra.cfn", "cfn") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/volume_encrypted_infra.rego b/rego/tests/lib/inputs/volume_encrypted_infra.rego new file mode 100644 index 00000000..d2dc52f6 --- /dev/null +++ b/rego/tests/lib/inputs/volume_encrypted_infra.rego @@ -0,0 +1,32 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This package was automatically generated from: +# +# tests/lib/inputs/volume_encrypted_infra.tf +# +# using 'generate_test_inputs.sh' and should not be modified +# directly. +# +# It provides three inputs for testing: +# - mock_input: The resource view input as passed to advanced rules +# - mock_resources: The resources present as a convenience for tests +# - mock_config: The raw config input as its parsed by regula +package tests.lib.inputs.volume_encrypted_infra + +import data.regula + +mock_config := regula_load_type("volume_encrypted_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_02.tf b/rego/tests/lib/inputs/volume_encrypted_infra.tf similarity index 52% rename from rego/tests/lib/inputs/resource_view_02.tf rename to rego/tests/lib/inputs/volume_encrypted_infra.tf index 2b5dd39f..5b248d54 100644 --- a/rego/tests/lib/inputs/resource_view_02.tf +++ b/rego/tests/lib/inputs/volume_encrypted_infra.tf @@ -1,4 +1,4 @@ -# Copyright 2020-2021 Fugue, Inc. +# Copyright 2020 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,30 +12,22 @@ # See the License for the specific language governing permissions and # limitations under the License. provider "aws" { - region = "us-west-2" + region = "us-east-2" } -resource "aws_s3_bucket" "example" { - bucket_prefix = "example" +resource "aws_ebs_volume" "good" { + availability_zone = "us-west-2a" + size = 40 + encrypted = true } -data "aws_iam_policy_document" "example" { - statement { - effect = "Allow" - actions = ["s3:*"] - - principals { - type = "*" - identifiers = ["*"] - } - - resources = [ - "arn:aws:s3:::some-example-bucket/*", - "arn:aws:s3:::${aws_s3_bucket.example.id}/*", - ] - } +resource "aws_ebs_volume" "missing" { + availability_zone = "us-west-2a" + size = 40 } -resource "aws_iam_policy" "example" { - policy = "${data.aws_iam_policy_document.example.json}" +resource "aws_ebs_volume" "bad" { + availability_zone = "us-west-2a" + size = 40 + encrypted = false } diff --git a/rego/tests/lib/inputs/volume_encrypted_infra.tfplan b/rego/tests/lib/inputs/volume_encrypted_infra.tfplan new file mode 100644 index 00000000..f58fa542 --- /dev/null +++ b/rego/tests/lib/inputs/volume_encrypted_infra.tfplan @@ -0,0 +1,222 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_ebs_volume.bad", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "bad", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "availability_zone": "us-west-2a", + "encrypted": false, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + } + }, + { + "address": "aws_ebs_volume.good", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "good", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "availability_zone": "us-west-2a", + "encrypted": true, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + } + }, + { + "address": "aws_ebs_volume.missing", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "missing", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "availability_zone": "us-west-2a", + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_ebs_volume.bad", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "bad", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "availability_zone": "us-west-2a", + "encrypted": false, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "iops": true, + "kms_key_id": true, + "snapshot_id": true, + "tags_all": true, + "throughput": true, + "type": true + } + } + }, + { + "address": "aws_ebs_volume.good", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "good", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "availability_zone": "us-west-2a", + "encrypted": true, + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + }, + "after_unknown": { + "arn": true, + "id": true, + "iops": true, + "kms_key_id": true, + "snapshot_id": true, + "tags_all": true, + "throughput": true, + "type": true + } + } + }, + { + "address": "aws_ebs_volume.missing", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "missing", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "availability_zone": "us-west-2a", + "multi_attach_enabled": null, + "outpost_arn": null, + "size": 40, + "tags": null + }, + "after_unknown": { + "arn": true, + "encrypted": true, + "id": true, + "iops": true, + "kms_key_id": true, + "snapshot_id": true, + "tags_all": true, + "throughput": true, + "type": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_ebs_volume.bad", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "bad", + "provider_config_key": "aws", + "expressions": { + "availability_zone": { + "constant_value": "us-west-2a" + }, + "encrypted": { + "constant_value": false + }, + "size": { + "constant_value": 40 + } + }, + "schema_version": 0 + }, + { + "address": "aws_ebs_volume.good", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "good", + "provider_config_key": "aws", + "expressions": { + "availability_zone": { + "constant_value": "us-west-2a" + }, + "encrypted": { + "constant_value": true + }, + "size": { + "constant_value": 40 + } + }, + "schema_version": 0 + }, + { + "address": "aws_ebs_volume.missing", + "mode": "managed", + "type": "aws_ebs_volume", + "name": "missing", + "provider_config_key": "aws", + "expressions": { + "availability_zone": { + "constant_value": "us-west-2a" + }, + "size": { + "constant_value": 40 + } + }, + "schema_version": 0 + } + ] + } + } +} From ff68e224768d89da5186fc1a340bbffd8f409ff8 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Mon, 3 May 2021 17:02:38 -0400 Subject: [PATCH 34/65] [RM-5352] Fix copy and paste issue in test messages --- pkg/rego/rego_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/rego/rego_test.go b/pkg/rego/rego_test.go index b2851519..09e6e8cd 100644 --- a/pkg/rego/rego_test.go +++ b/pkg/rego/rego_test.go @@ -69,15 +69,15 @@ func TestRegulaRules(t *testing.T) { return nil } if err := rego.LoadRegula(false, cb); err != nil { - assert.Fail(t, "Failed to load regula library", err) + assert.Fail(t, "Failed to load regula library and rules", err) } if err := rego.LoadOSFiles([]string{"../../rego/tests/rules"}, cb); err != nil { - assert.Fail(t, "Failed to load regula library tests", err) + assert.Fail(t, "Failed to load regula rule tests", err) } ctx := context.Background() ch, err := tester.NewRunner().SetStore(inmem.New()).Run(ctx, modules) if err != nil { - assert.Fail(t, "Failed to run library tests through OPA", err) + assert.Fail(t, "Failed to run rule tests through OPA", err) } failedTests := []string{} hasFailures := false From 64d6648edc01c02965498a2cf70c9d3ed144f3d8 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 4 May 2021 09:20:55 -0400 Subject: [PATCH 35/65] [RM-5352] Update and include examples tests in go test --- Makefile | 2 +- cmd/test.go | 9 +- pkg/rego/rego_test.go | 56 +- pkg/rego/runtest.go | 10 +- .../aws/inputs/ec2_t2_only_infra.rego | 928 +-------------- .../aws/inputs/ec2_t2_only_infra.tfplan | 1008 +++++++++++++++++ .../aws/inputs/iam_password_length_infra.rego | 199 +--- .../inputs/iam_password_length_infra.tfplan | 182 +++ .../aws/inputs/tag_all_resources_infra.rego | 357 +----- .../aws/inputs/tag_all_resources_infra.tfplan | 364 ++++++ .../aws/inputs/useast1_only_infra.rego | 37 +- .../aws/inputs/useast1_only_infra.tfplan | 20 + rego/tests/lib/fugue_resource_view_test.rego | 4 +- 13 files changed, 1642 insertions(+), 1534 deletions(-) create mode 100644 rego/tests/examples/aws/inputs/ec2_t2_only_infra.tfplan create mode 100644 rego/tests/examples/aws/inputs/iam_password_length_infra.tfplan create mode 100644 rego/tests/examples/aws/inputs/tag_all_resources_infra.tfplan create mode 100644 rego/tests/examples/aws/inputs/useast1_only_infra.tfplan diff --git a/Makefile b/Makefile index dc18b3f3..c140983e 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ clean: rm -f $(BINARY) $(BINARY)-linux-amd64 $(BINARY)-darwin-amd64 .PHONY: test -test: +test: $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) go test -v -cover ./... .PHONY: coverage diff --git a/cmd/test.go b/cmd/test.go index c65c975a..6d7317e8 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -30,10 +30,16 @@ func NewTestCommand() *cobra.Command { Use: "test [rego paths]", Short: "Run OPA test with Regula.", Run: func(cmd *cobra.Command, includes []string) { + trace, err := cmd.Flags().GetBool("trace") + if err != nil { + fmt.Println(err) + os.Exit(1) + } ctx := context.TODO() - err := rego.RunTest(®o.RunTestOptions{ + err = rego.RunTest(®o.RunTestOptions{ Ctx: ctx, Includes: includes, + Trace: trace, }) if err != nil { fmt.Println(err) @@ -41,6 +47,7 @@ func NewTestCommand() *cobra.Command { } }, } + cmd.Flags().BoolP("trace", "t", false, "Enable trace output") return cmd } diff --git a/pkg/rego/rego_test.go b/pkg/rego/rego_test.go index 09e6e8cd..6d38a82d 100644 --- a/pkg/rego/rego_test.go +++ b/pkg/rego/rego_test.go @@ -17,7 +17,7 @@ func formatFailedTest(r *tester.Result) string { return fmt.Sprintf("%s.%s in file %s", r.Package, r.Name, r.Location.String()) } -func TestRegulaLib(t *testing.T) { +func runRegoTest(t *testing.T, userOnly bool, includes []string) { rego.RegisterBuiltins() modules := map[string]*ast.Module{} cb := func(r rego.RegoFile) error { @@ -28,16 +28,16 @@ func TestRegulaLib(t *testing.T) { modules[r.Path()] = module return nil } - if err := rego.LoadRegula(true, cb); err != nil { - assert.Fail(t, "Failed to load regula library", err) + if err := rego.LoadRegula(userOnly, cb); err != nil { + assert.Fail(t, "Failed to load regula", userOnly, err) } - if err := rego.LoadOSFiles([]string{"../../rego/tests/lib"}, cb); err != nil { - assert.Fail(t, "Failed to load regula library tests", err) + if err := rego.LoadOSFiles(includes, cb); err != nil { + assert.Fail(t, "Failed to load regula tests", err) } ctx := context.Background() ch, err := tester.NewRunner().SetStore(inmem.New()).Run(ctx, modules) if err != nil { - assert.Fail(t, "Failed to run library tests through OPA", err) + assert.Fail(t, "Failed to run tests through OPA", err) } failedTests := []string{} hasFailures := false @@ -57,42 +57,14 @@ func TestRegulaLib(t *testing.T) { assert.Falsef(t, hasFailures, "Some tests failed:\n%v", strings.Join(failedTests, "\n")) } +func TestRegulaLib(t *testing.T) { + runRegoTest(t, true, []string{"../../rego/tests/lib"}) +} + func TestRegulaRules(t *testing.T) { - rego.RegisterBuiltins() - modules := map[string]*ast.Module{} - cb := func(r rego.RegoFile) error { - module, err := r.AstModule() - if err != nil { - return err - } - modules[r.Path()] = module - return nil - } - if err := rego.LoadRegula(false, cb); err != nil { - assert.Fail(t, "Failed to load regula library and rules", err) - } - if err := rego.LoadOSFiles([]string{"../../rego/tests/rules"}, cb); err != nil { - assert.Fail(t, "Failed to load regula rule tests", err) - } - ctx := context.Background() - ch, err := tester.NewRunner().SetStore(inmem.New()).Run(ctx, modules) - if err != nil { - assert.Fail(t, "Failed to run rule tests through OPA", err) - } - failedTests := []string{} - hasFailures := false - errors := []error{} - for r := range ch { - if r.Fail { - hasFailures = true - failedTests = append(failedTests, formatFailedTest(r)) - } - hasFailures = hasFailures || r.Fail - if r.Error != nil { - errors = append(errors, r.Error) - } - } + runRegoTest(t, false, []string{"../../rego/tests/rules"}) +} - assert.Empty(t, errors) - assert.Falsef(t, hasFailures, "Some tests failed:\n%v", strings.Join(failedTests, "\n")) +func TestRegulaExamples(t *testing.T) { + runRegoTest(t, true, []string{"../../rego/examples", "../../rego/tests/examples"}) } diff --git a/pkg/rego/runtest.go b/pkg/rego/runtest.go index 38cf7241..cd1bd1b8 100644 --- a/pkg/rego/runtest.go +++ b/pkg/rego/runtest.go @@ -12,11 +12,11 @@ import ( type RunTestOptions struct { Ctx context.Context Includes []string + Trace bool } func RunTest(options *RunTestOptions) error { RegisterBuiltins() - store := inmem.New() modules := map[string]*ast.Module{} cb := func(r RegoFile) error { module, err := r.AstModule() @@ -32,14 +32,18 @@ func RunTest(options *RunTestOptions) error { if err := LoadOSFiles(options.Includes, cb); err != nil { return err } - ch, err := tester.NewRunner().SetStore(store).EnableTracing(true).Run(options.Ctx, modules) + ch, err := tester. + NewRunner(). + SetStore(inmem.New()). + EnableTracing(options.Trace). + Run(options.Ctx, modules) if err != nil { return err } reporter := tester.PrettyReporter{ Output: os.Stdout, FailureLine: true, - Verbose: true, + Verbose: options.Trace, } reporter.Report(ch) return nil diff --git a/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego index f04bd433..c494cb1d 100644 --- a/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego +++ b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,927 +16,17 @@ # # tests/examples/aws/inputs/ec2_t2_only_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.ec2_t2_only_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_instance.invalid", - "mode": "managed", - "type": "aws_instance", - "name": "invalid", - "provider_name": "aws", - "schema_version": 1, - "values": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.nano", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - } - }, - { - "address": "aws_instance.valid_2xlarge", - "mode": "managed", - "type": "aws_instance", - "name": "valid_2xlarge", - "provider_name": "aws", - "schema_version": 1, - "values": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.2xlarge", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - } - }, - { - "address": "aws_instance.valid_large", - "mode": "managed", - "type": "aws_instance", - "name": "valid_large", - "provider_name": "aws", - "schema_version": 1, - "values": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.large", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - } - }, - { - "address": "aws_instance.valid_medium", - "mode": "managed", - "type": "aws_instance", - "name": "valid_medium", - "provider_name": "aws", - "schema_version": 1, - "values": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.medium", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - } - }, - { - "address": "aws_instance.valid_micro", - "mode": "managed", - "type": "aws_instance", - "name": "valid_micro", - "provider_name": "aws", - "schema_version": 1, - "values": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.micro", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - } - }, - { - "address": "aws_instance.valid_small", - "mode": "managed", - "type": "aws_instance", - "name": "valid_small", - "provider_name": "aws", - "schema_version": 1, - "values": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.small", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - } - }, - { - "address": "aws_instance.valid_xlarge", - "mode": "managed", - "type": "aws_instance", - "name": "valid_xlarge", - "provider_name": "aws", - "schema_version": 1, - "values": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.xlarge", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - } - }, - { - "address": "data.aws_ami.ubuntu", - "mode": "data", - "type": "aws_ami", - "name": "ubuntu", - "provider_name": "aws", - "schema_version": 0, - "values": { - "executable_users": null, - "filter": [ - { - "name": "name", - "values": [ - "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*" - ] - }, - { - "name": "virtualization-type", - "values": [ - "hvm" - ] - } - ], - "most_recent": true, - "name_regex": null, - "owners": [ - "099720109477" - ] - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_instance.invalid", - "mode": "managed", - "type": "aws_instance", - "name": "invalid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.nano", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - }, - "after_unknown": { - "ami": true, - "arn": true, - "associate_public_ip_address": true, - "availability_zone": true, - "cpu_core_count": true, - "cpu_threads_per_core": true, - "credit_specification": [], - "ebs_block_device": true, - "ephemeral_block_device": true, - "host_id": true, - "id": true, - "instance_state": true, - "ipv6_address_count": true, - "ipv6_addresses": true, - "key_name": true, - "metadata_options": true, - "network_interface": true, - "outpost_arn": true, - "password_data": true, - "placement_group": true, - "primary_network_interface_id": true, - "private_dns": true, - "private_ip": true, - "public_dns": true, - "public_ip": true, - "root_block_device": true, - "secondary_private_ips": true, - "security_groups": true, - "subnet_id": true, - "tenancy": true, - "volume_tags": true, - "vpc_security_group_ids": true - } - } - }, - { - "address": "aws_instance.valid_2xlarge", - "mode": "managed", - "type": "aws_instance", - "name": "valid_2xlarge", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.2xlarge", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - }, - "after_unknown": { - "ami": true, - "arn": true, - "associate_public_ip_address": true, - "availability_zone": true, - "cpu_core_count": true, - "cpu_threads_per_core": true, - "credit_specification": [], - "ebs_block_device": true, - "ephemeral_block_device": true, - "host_id": true, - "id": true, - "instance_state": true, - "ipv6_address_count": true, - "ipv6_addresses": true, - "key_name": true, - "metadata_options": true, - "network_interface": true, - "outpost_arn": true, - "password_data": true, - "placement_group": true, - "primary_network_interface_id": true, - "private_dns": true, - "private_ip": true, - "public_dns": true, - "public_ip": true, - "root_block_device": true, - "secondary_private_ips": true, - "security_groups": true, - "subnet_id": true, - "tenancy": true, - "volume_tags": true, - "vpc_security_group_ids": true - } - } - }, - { - "address": "aws_instance.valid_large", - "mode": "managed", - "type": "aws_instance", - "name": "valid_large", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.large", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - }, - "after_unknown": { - "ami": true, - "arn": true, - "associate_public_ip_address": true, - "availability_zone": true, - "cpu_core_count": true, - "cpu_threads_per_core": true, - "credit_specification": [], - "ebs_block_device": true, - "ephemeral_block_device": true, - "host_id": true, - "id": true, - "instance_state": true, - "ipv6_address_count": true, - "ipv6_addresses": true, - "key_name": true, - "metadata_options": true, - "network_interface": true, - "outpost_arn": true, - "password_data": true, - "placement_group": true, - "primary_network_interface_id": true, - "private_dns": true, - "private_ip": true, - "public_dns": true, - "public_ip": true, - "root_block_device": true, - "secondary_private_ips": true, - "security_groups": true, - "subnet_id": true, - "tenancy": true, - "volume_tags": true, - "vpc_security_group_ids": true - } - } - }, - { - "address": "aws_instance.valid_medium", - "mode": "managed", - "type": "aws_instance", - "name": "valid_medium", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.medium", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - }, - "after_unknown": { - "ami": true, - "arn": true, - "associate_public_ip_address": true, - "availability_zone": true, - "cpu_core_count": true, - "cpu_threads_per_core": true, - "credit_specification": [], - "ebs_block_device": true, - "ephemeral_block_device": true, - "host_id": true, - "id": true, - "instance_state": true, - "ipv6_address_count": true, - "ipv6_addresses": true, - "key_name": true, - "metadata_options": true, - "network_interface": true, - "outpost_arn": true, - "password_data": true, - "placement_group": true, - "primary_network_interface_id": true, - "private_dns": true, - "private_ip": true, - "public_dns": true, - "public_ip": true, - "root_block_device": true, - "secondary_private_ips": true, - "security_groups": true, - "subnet_id": true, - "tenancy": true, - "volume_tags": true, - "vpc_security_group_ids": true - } - } - }, - { - "address": "aws_instance.valid_micro", - "mode": "managed", - "type": "aws_instance", - "name": "valid_micro", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.micro", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - }, - "after_unknown": { - "ami": true, - "arn": true, - "associate_public_ip_address": true, - "availability_zone": true, - "cpu_core_count": true, - "cpu_threads_per_core": true, - "credit_specification": [], - "ebs_block_device": true, - "ephemeral_block_device": true, - "host_id": true, - "id": true, - "instance_state": true, - "ipv6_address_count": true, - "ipv6_addresses": true, - "key_name": true, - "metadata_options": true, - "network_interface": true, - "outpost_arn": true, - "password_data": true, - "placement_group": true, - "primary_network_interface_id": true, - "private_dns": true, - "private_ip": true, - "public_dns": true, - "public_ip": true, - "root_block_device": true, - "secondary_private_ips": true, - "security_groups": true, - "subnet_id": true, - "tenancy": true, - "volume_tags": true, - "vpc_security_group_ids": true - } - } - }, - { - "address": "aws_instance.valid_small", - "mode": "managed", - "type": "aws_instance", - "name": "valid_small", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.small", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - }, - "after_unknown": { - "ami": true, - "arn": true, - "associate_public_ip_address": true, - "availability_zone": true, - "cpu_core_count": true, - "cpu_threads_per_core": true, - "credit_specification": [], - "ebs_block_device": true, - "ephemeral_block_device": true, - "host_id": true, - "id": true, - "instance_state": true, - "ipv6_address_count": true, - "ipv6_addresses": true, - "key_name": true, - "metadata_options": true, - "network_interface": true, - "outpost_arn": true, - "password_data": true, - "placement_group": true, - "primary_network_interface_id": true, - "private_dns": true, - "private_ip": true, - "public_dns": true, - "public_ip": true, - "root_block_device": true, - "secondary_private_ips": true, - "security_groups": true, - "subnet_id": true, - "tenancy": true, - "volume_tags": true, - "vpc_security_group_ids": true - } - } - }, - { - "address": "aws_instance.valid_xlarge", - "mode": "managed", - "type": "aws_instance", - "name": "valid_xlarge", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "credit_specification": [], - "disable_api_termination": null, - "ebs_optimized": null, - "get_password_data": false, - "hibernation": null, - "iam_instance_profile": null, - "instance_initiated_shutdown_behavior": null, - "instance_type": "t2.xlarge", - "monitoring": null, - "source_dest_check": true, - "tags": null, - "timeouts": null, - "user_data": null, - "user_data_base64": null - }, - "after_unknown": { - "ami": true, - "arn": true, - "associate_public_ip_address": true, - "availability_zone": true, - "cpu_core_count": true, - "cpu_threads_per_core": true, - "credit_specification": [], - "ebs_block_device": true, - "ephemeral_block_device": true, - "host_id": true, - "id": true, - "instance_state": true, - "ipv6_address_count": true, - "ipv6_addresses": true, - "key_name": true, - "metadata_options": true, - "network_interface": true, - "outpost_arn": true, - "password_data": true, - "placement_group": true, - "primary_network_interface_id": true, - "private_dns": true, - "private_ip": true, - "public_dns": true, - "public_ip": true, - "root_block_device": true, - "secondary_private_ips": true, - "security_groups": true, - "subnet_id": true, - "tenancy": true, - "volume_tags": true, - "vpc_security_group_ids": true - } - } - }, - { - "address": "data.aws_ami.ubuntu", - "mode": "data", - "type": "aws_ami", - "name": "ubuntu", - "provider_name": "aws", - "change": { - "actions": [ - "read" - ], - "before": null, - "after": { - "executable_users": null, - "filter": [ - { - "name": "name", - "values": [ - "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*" - ] - }, - { - "name": "virtualization-type", - "values": [ - "hvm" - ] - } - ], - "most_recent": true, - "name_regex": null, - "owners": [ - "099720109477" - ] - }, - "after_unknown": { - "architecture": true, - "arn": true, - "block_device_mappings": true, - "creation_date": true, - "description": true, - "filter": [ - { - "values": [ - false - ] - }, - { - "values": [ - false - ] - } - ], - "hypervisor": true, - "id": true, - "image_id": true, - "image_location": true, - "image_owner_alias": true, - "image_type": true, - "kernel_id": true, - "name": true, - "owner_id": true, - "owners": [ - false - ], - "platform": true, - "product_codes": true, - "public": true, - "ramdisk_id": true, - "root_device_name": true, - "root_device_type": true, - "root_snapshot_id": true, - "sriov_net_support": true, - "state": true, - "state_reason": true, - "tags": true, - "virtualization_type": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-west-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_instance.invalid", - "mode": "managed", - "type": "aws_instance", - "name": "invalid", - "provider_config_key": "aws", - "expressions": { - "ami": { - "references": [ - "data.aws_ami.ubuntu" - ] - }, - "instance_type": { - "constant_value": "t2.nano" - } - }, - "schema_version": 1 - }, - { - "address": "aws_instance.valid_2xlarge", - "mode": "managed", - "type": "aws_instance", - "name": "valid_2xlarge", - "provider_config_key": "aws", - "expressions": { - "ami": { - "references": [ - "data.aws_ami.ubuntu" - ] - }, - "instance_type": { - "constant_value": "t2.2xlarge" - } - }, - "schema_version": 1 - }, - { - "address": "aws_instance.valid_large", - "mode": "managed", - "type": "aws_instance", - "name": "valid_large", - "provider_config_key": "aws", - "expressions": { - "ami": { - "references": [ - "data.aws_ami.ubuntu" - ] - }, - "instance_type": { - "constant_value": "t2.large" - } - }, - "schema_version": 1 - }, - { - "address": "aws_instance.valid_medium", - "mode": "managed", - "type": "aws_instance", - "name": "valid_medium", - "provider_config_key": "aws", - "expressions": { - "ami": { - "references": [ - "data.aws_ami.ubuntu" - ] - }, - "instance_type": { - "constant_value": "t2.medium" - } - }, - "schema_version": 1 - }, - { - "address": "aws_instance.valid_micro", - "mode": "managed", - "type": "aws_instance", - "name": "valid_micro", - "provider_config_key": "aws", - "expressions": { - "ami": { - "references": [ - "data.aws_ami.ubuntu" - ] - }, - "instance_type": { - "constant_value": "t2.micro" - } - }, - "schema_version": 1 - }, - { - "address": "aws_instance.valid_small", - "mode": "managed", - "type": "aws_instance", - "name": "valid_small", - "provider_config_key": "aws", - "expressions": { - "ami": { - "references": [ - "data.aws_ami.ubuntu" - ] - }, - "instance_type": { - "constant_value": "t2.small" - } - }, - "schema_version": 1 - }, - { - "address": "aws_instance.valid_xlarge", - "mode": "managed", - "type": "aws_instance", - "name": "valid_xlarge", - "provider_config_key": "aws", - "expressions": { - "ami": { - "references": [ - "data.aws_ami.ubuntu" - ] - }, - "instance_type": { - "constant_value": "t2.xlarge" - } - }, - "schema_version": 1 - }, - { - "address": "data.aws_ami.ubuntu", - "mode": "data", - "type": "aws_ami", - "name": "ubuntu", - "provider_config_key": "aws", - "expressions": { - "filter": [ - { - "name": { - "constant_value": "name" - }, - "values": { - "constant_value": [ - "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*" - ] - } - }, - { - "name": { - "constant_value": "virtualization-type" - }, - "values": { - "constant_value": [ - "hvm" - ] - } - } - ], - "most_recent": { - "constant_value": true - }, - "owners": { - "constant_value": [ - "099720109477" - ] - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("ec2_t2_only_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/examples/aws/inputs/ec2_t2_only_infra.tfplan b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.tfplan new file mode 100644 index 00000000..3ec1550b --- /dev/null +++ b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.tfplan @@ -0,0 +1,1008 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_instance.invalid", + "mode": "managed", + "type": "aws_instance", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.nano", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + } + }, + { + "address": "aws_instance.valid_2xlarge", + "mode": "managed", + "type": "aws_instance", + "name": "valid_2xlarge", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.2xlarge", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + } + }, + { + "address": "aws_instance.valid_large", + "mode": "managed", + "type": "aws_instance", + "name": "valid_large", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.large", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + } + }, + { + "address": "aws_instance.valid_medium", + "mode": "managed", + "type": "aws_instance", + "name": "valid_medium", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.medium", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + } + }, + { + "address": "aws_instance.valid_micro", + "mode": "managed", + "type": "aws_instance", + "name": "valid_micro", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.micro", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + } + }, + { + "address": "aws_instance.valid_small", + "mode": "managed", + "type": "aws_instance", + "name": "valid_small", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.small", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + } + }, + { + "address": "aws_instance.valid_xlarge", + "mode": "managed", + "type": "aws_instance", + "name": "valid_xlarge", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.xlarge", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + } + }, + { + "address": "data.aws_ami.ubuntu", + "mode": "data", + "type": "aws_ami", + "name": "ubuntu", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "architecture": "x86_64", + "arn": "arn:aws:ec2:us-west-2::image/ami-0bac6fc47ad07c5f5", + "block_device_mappings": [ + { + "device_name": "/dev/sda1", + "ebs": { + "delete_on_termination": "true", + "encrypted": "false", + "iops": "0", + "snapshot_id": "snap-013fb4433bd2108c7", + "throughput": "0", + "volume_size": "8", + "volume_type": "gp2" + }, + "no_device": "", + "virtual_name": "" + }, + { + "device_name": "/dev/sdb", + "ebs": {}, + "no_device": "", + "virtual_name": "ephemeral0" + }, + { + "device_name": "/dev/sdc", + "ebs": {}, + "no_device": "", + "virtual_name": "ephemeral1" + } + ], + "creation_date": "2019-11-11T13:13:47.000Z", + "description": "Canonical, Ubuntu, 14.04 LTS, amd64 trusty image build on 2019-11-07", + "ena_support": true, + "executable_users": null, + "filter": [ + { + "name": "name", + "values": [ + "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*" + ] + }, + { + "name": "virtualization-type", + "values": [ + "hvm" + ] + } + ], + "hypervisor": "xen", + "id": "ami-0bac6fc47ad07c5f5", + "image_id": "ami-0bac6fc47ad07c5f5", + "image_location": "099720109477/ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20191107", + "image_owner_alias": null, + "image_type": "machine", + "kernel_id": null, + "most_recent": true, + "name": "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20191107", + "name_regex": null, + "owner_id": "099720109477", + "owners": [ + "099720109477" + ], + "platform": null, + "platform_details": "Linux/UNIX", + "product_codes": [], + "public": true, + "ramdisk_id": null, + "root_device_name": "/dev/sda1", + "root_device_type": "ebs", + "root_snapshot_id": "snap-013fb4433bd2108c7", + "sriov_net_support": "simple", + "state": "available", + "state_reason": { + "code": "UNSET", + "message": "UNSET" + }, + "tags": {}, + "usage_operation": "RunInstances", + "virtualization_type": "hvm" + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_instance.invalid", + "mode": "managed", + "type": "aws_instance", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.nano", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": true, + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": true, + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + } + } + }, + { + "address": "aws_instance.valid_2xlarge", + "mode": "managed", + "type": "aws_instance", + "name": "valid_2xlarge", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.2xlarge", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": true, + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": true, + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + } + } + }, + { + "address": "aws_instance.valid_large", + "mode": "managed", + "type": "aws_instance", + "name": "valid_large", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.large", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": true, + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": true, + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + } + } + }, + { + "address": "aws_instance.valid_medium", + "mode": "managed", + "type": "aws_instance", + "name": "valid_medium", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.medium", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": true, + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": true, + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + } + } + }, + { + "address": "aws_instance.valid_micro", + "mode": "managed", + "type": "aws_instance", + "name": "valid_micro", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.micro", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": true, + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": true, + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + } + } + }, + { + "address": "aws_instance.valid_small", + "mode": "managed", + "type": "aws_instance", + "name": "valid_small", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.small", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": true, + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": true, + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + } + } + }, + { + "address": "aws_instance.valid_xlarge", + "mode": "managed", + "type": "aws_instance", + "name": "valid_xlarge", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "ami": "ami-0bac6fc47ad07c5f5", + "credit_specification": [], + "disable_api_termination": null, + "ebs_optimized": null, + "get_password_data": false, + "hibernation": null, + "iam_instance_profile": null, + "instance_type": "t2.xlarge", + "monitoring": null, + "source_dest_check": true, + "tags": null, + "timeouts": null, + "user_data": null, + "user_data_base64": null, + "volume_tags": null + }, + "after_unknown": { + "arn": true, + "associate_public_ip_address": true, + "availability_zone": true, + "cpu_core_count": true, + "cpu_threads_per_core": true, + "credit_specification": [], + "ebs_block_device": true, + "enclave_options": true, + "ephemeral_block_device": true, + "host_id": true, + "id": true, + "instance_initiated_shutdown_behavior": true, + "instance_state": true, + "ipv6_address_count": true, + "ipv6_addresses": true, + "key_name": true, + "metadata_options": true, + "network_interface": true, + "outpost_arn": true, + "password_data": true, + "placement_group": true, + "primary_network_interface_id": true, + "private_dns": true, + "private_ip": true, + "public_dns": true, + "public_ip": true, + "root_block_device": true, + "secondary_private_ips": true, + "security_groups": true, + "subnet_id": true, + "tags_all": true, + "tenancy": true, + "vpc_security_group_ids": true + } + } + }, + { + "address": "data.aws_ami.ubuntu", + "mode": "data", + "type": "aws_ami", + "name": "ubuntu", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "read" + ], + "before": null, + "after": { + "architecture": "x86_64", + "arn": "arn:aws:ec2:us-west-2::image/ami-0bac6fc47ad07c5f5", + "block_device_mappings": [ + { + "device_name": "/dev/sda1", + "ebs": { + "delete_on_termination": "true", + "encrypted": "false", + "iops": "0", + "snapshot_id": "snap-013fb4433bd2108c7", + "throughput": "0", + "volume_size": "8", + "volume_type": "gp2" + }, + "no_device": "", + "virtual_name": "" + }, + { + "device_name": "/dev/sdb", + "ebs": {}, + "no_device": "", + "virtual_name": "ephemeral0" + }, + { + "device_name": "/dev/sdc", + "ebs": {}, + "no_device": "", + "virtual_name": "ephemeral1" + } + ], + "creation_date": "2019-11-11T13:13:47.000Z", + "description": "Canonical, Ubuntu, 14.04 LTS, amd64 trusty image build on 2019-11-07", + "ena_support": true, + "executable_users": null, + "filter": [ + { + "name": "name", + "values": [ + "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*" + ] + }, + { + "name": "virtualization-type", + "values": [ + "hvm" + ] + } + ], + "hypervisor": "xen", + "id": "ami-0bac6fc47ad07c5f5", + "image_id": "ami-0bac6fc47ad07c5f5", + "image_location": "099720109477/ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20191107", + "image_owner_alias": null, + "image_type": "machine", + "kernel_id": null, + "most_recent": true, + "name": "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-20191107", + "name_regex": null, + "owner_id": "099720109477", + "owners": [ + "099720109477" + ], + "platform": null, + "platform_details": "Linux/UNIX", + "product_codes": [], + "public": true, + "ramdisk_id": null, + "root_device_name": "/dev/sda1", + "root_device_type": "ebs", + "root_snapshot_id": "snap-013fb4433bd2108c7", + "sriov_net_support": "simple", + "state": "available", + "state_reason": { + "code": "UNSET", + "message": "UNSET" + }, + "tags": {}, + "usage_operation": "RunInstances", + "virtualization_type": "hvm" + }, + "after_unknown": {} + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-west-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_instance.invalid", + "mode": "managed", + "type": "aws_instance", + "name": "invalid", + "provider_config_key": "aws", + "expressions": { + "ami": { + "references": [ + "data.aws_ami.ubuntu" + ] + }, + "instance_type": { + "constant_value": "t2.nano" + } + }, + "schema_version": 1 + }, + { + "address": "aws_instance.valid_2xlarge", + "mode": "managed", + "type": "aws_instance", + "name": "valid_2xlarge", + "provider_config_key": "aws", + "expressions": { + "ami": { + "references": [ + "data.aws_ami.ubuntu" + ] + }, + "instance_type": { + "constant_value": "t2.2xlarge" + } + }, + "schema_version": 1 + }, + { + "address": "aws_instance.valid_large", + "mode": "managed", + "type": "aws_instance", + "name": "valid_large", + "provider_config_key": "aws", + "expressions": { + "ami": { + "references": [ + "data.aws_ami.ubuntu" + ] + }, + "instance_type": { + "constant_value": "t2.large" + } + }, + "schema_version": 1 + }, + { + "address": "aws_instance.valid_medium", + "mode": "managed", + "type": "aws_instance", + "name": "valid_medium", + "provider_config_key": "aws", + "expressions": { + "ami": { + "references": [ + "data.aws_ami.ubuntu" + ] + }, + "instance_type": { + "constant_value": "t2.medium" + } + }, + "schema_version": 1 + }, + { + "address": "aws_instance.valid_micro", + "mode": "managed", + "type": "aws_instance", + "name": "valid_micro", + "provider_config_key": "aws", + "expressions": { + "ami": { + "references": [ + "data.aws_ami.ubuntu" + ] + }, + "instance_type": { + "constant_value": "t2.micro" + } + }, + "schema_version": 1 + }, + { + "address": "aws_instance.valid_small", + "mode": "managed", + "type": "aws_instance", + "name": "valid_small", + "provider_config_key": "aws", + "expressions": { + "ami": { + "references": [ + "data.aws_ami.ubuntu" + ] + }, + "instance_type": { + "constant_value": "t2.small" + } + }, + "schema_version": 1 + }, + { + "address": "aws_instance.valid_xlarge", + "mode": "managed", + "type": "aws_instance", + "name": "valid_xlarge", + "provider_config_key": "aws", + "expressions": { + "ami": { + "references": [ + "data.aws_ami.ubuntu" + ] + }, + "instance_type": { + "constant_value": "t2.xlarge" + } + }, + "schema_version": 1 + }, + { + "address": "data.aws_ami.ubuntu", + "mode": "data", + "type": "aws_ami", + "name": "ubuntu", + "provider_config_key": "aws", + "expressions": { + "filter": [ + { + "name": { + "constant_value": "name" + }, + "values": { + "constant_value": [ + "ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*" + ] + } + }, + { + "name": { + "constant_value": "virtualization-type" + }, + "values": { + "constant_value": [ + "hvm" + ] + } + } + ], + "most_recent": { + "constant_value": true + }, + "owners": { + "constant_value": [ + "099720109477" + ] + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/examples/aws/inputs/iam_password_length_infra.rego b/rego/tests/examples/aws/inputs/iam_password_length_infra.rego index 98797c0a..20f0699f 100644 --- a/rego/tests/examples/aws/inputs/iam_password_length_infra.rego +++ b/rego/tests/examples/aws/inputs/iam_password_length_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,198 +16,17 @@ # # tests/examples/aws/inputs/iam_password_length_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.iam_password_length_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_iam_account_password_policy.invalid_1", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "invalid_1", - "provider_name": "aws", - "schema_version": 0, - "values": { - "allow_users_to_change_password": true, - "minimum_password_length": 4 - } - }, - { - "address": "aws_iam_account_password_policy.invalid_2", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "invalid_2", - "provider_name": "aws", - "schema_version": 0, - "values": { - "allow_users_to_change_password": true, - "minimum_password_length": 6 - } - }, - { - "address": "aws_iam_account_password_policy.valid", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "valid", - "provider_name": "aws", - "schema_version": 0, - "values": { - "allow_users_to_change_password": true, - "minimum_password_length": 8 - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_iam_account_password_policy.invalid_1", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "invalid_1", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow_users_to_change_password": true, - "minimum_password_length": 4 - }, - "after_unknown": { - "expire_passwords": true, - "hard_expiry": true, - "id": true, - "max_password_age": true, - "password_reuse_prevention": true, - "require_lowercase_characters": true, - "require_numbers": true, - "require_symbols": true, - "require_uppercase_characters": true - } - } - }, - { - "address": "aws_iam_account_password_policy.invalid_2", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "invalid_2", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow_users_to_change_password": true, - "minimum_password_length": 6 - }, - "after_unknown": { - "expire_passwords": true, - "hard_expiry": true, - "id": true, - "max_password_age": true, - "password_reuse_prevention": true, - "require_lowercase_characters": true, - "require_numbers": true, - "require_symbols": true, - "require_uppercase_characters": true - } - } - }, - { - "address": "aws_iam_account_password_policy.valid", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "valid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "allow_users_to_change_password": true, - "minimum_password_length": 8 - }, - "after_unknown": { - "expire_passwords": true, - "hard_expiry": true, - "id": true, - "max_password_age": true, - "password_reuse_prevention": true, - "require_lowercase_characters": true, - "require_numbers": true, - "require_symbols": true, - "require_uppercase_characters": true - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-2" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_iam_account_password_policy.invalid_1", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "invalid_1", - "provider_config_key": "aws", - "expressions": { - "minimum_password_length": { - "constant_value": 4 - } - }, - "schema_version": 0 - }, - { - "address": "aws_iam_account_password_policy.invalid_2", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "invalid_2", - "provider_config_key": "aws", - "schema_version": 0 - }, - { - "address": "aws_iam_account_password_policy.valid", - "mode": "managed", - "type": "aws_iam_account_password_policy", - "name": "valid", - "provider_config_key": "aws", - "expressions": { - "minimum_password_length": { - "constant_value": 8 - } - }, - "schema_version": 0 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("iam_password_length_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/examples/aws/inputs/iam_password_length_infra.tfplan b/rego/tests/examples/aws/inputs/iam_password_length_infra.tfplan new file mode 100644 index 00000000..a56b0d76 --- /dev/null +++ b/rego/tests/examples/aws/inputs/iam_password_length_infra.tfplan @@ -0,0 +1,182 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_iam_account_password_policy.invalid_1", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "invalid_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "allow_users_to_change_password": true, + "minimum_password_length": 4 + } + }, + { + "address": "aws_iam_account_password_policy.invalid_2", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "invalid_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "allow_users_to_change_password": true, + "minimum_password_length": 6 + } + }, + { + "address": "aws_iam_account_password_policy.valid", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "valid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "allow_users_to_change_password": true, + "minimum_password_length": 8 + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_iam_account_password_policy.invalid_1", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "invalid_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow_users_to_change_password": true, + "minimum_password_length": 4 + }, + "after_unknown": { + "expire_passwords": true, + "hard_expiry": true, + "id": true, + "max_password_age": true, + "password_reuse_prevention": true, + "require_lowercase_characters": true, + "require_numbers": true, + "require_symbols": true, + "require_uppercase_characters": true + } + } + }, + { + "address": "aws_iam_account_password_policy.invalid_2", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "invalid_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow_users_to_change_password": true, + "minimum_password_length": 6 + }, + "after_unknown": { + "expire_passwords": true, + "hard_expiry": true, + "id": true, + "max_password_age": true, + "password_reuse_prevention": true, + "require_lowercase_characters": true, + "require_numbers": true, + "require_symbols": true, + "require_uppercase_characters": true + } + } + }, + { + "address": "aws_iam_account_password_policy.valid", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "valid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "allow_users_to_change_password": true, + "minimum_password_length": 8 + }, + "after_unknown": { + "expire_passwords": true, + "hard_expiry": true, + "id": true, + "max_password_age": true, + "password_reuse_prevention": true, + "require_lowercase_characters": true, + "require_numbers": true, + "require_symbols": true, + "require_uppercase_characters": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-2" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_iam_account_password_policy.invalid_1", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "invalid_1", + "provider_config_key": "aws", + "expressions": { + "minimum_password_length": { + "constant_value": 4 + } + }, + "schema_version": 0 + }, + { + "address": "aws_iam_account_password_policy.invalid_2", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "invalid_2", + "provider_config_key": "aws", + "schema_version": 0 + }, + { + "address": "aws_iam_account_password_policy.valid", + "mode": "managed", + "type": "aws_iam_account_password_policy", + "name": "valid", + "provider_config_key": "aws", + "expressions": { + "minimum_password_length": { + "constant_value": 8 + } + }, + "schema_version": 0 + } + ] + } + } +} diff --git a/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego b/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego index 3d0d2e1a..6b93500a 100644 --- a/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego +++ b/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,356 +16,17 @@ # # tests/examples/aws/inputs/tag_all_resources_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.tag_all_resources_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": { - "resources": [ - { - "address": "aws_s3_bucket.invalid", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "invalid", - "provider_name": "aws", - "schema_version": 0, - "values": { - "acl": "private", - "bucket": "my-tf-test-bucket", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": { - "Environment": "Dev", - "Name": "My bucket" - }, - "website": [] - } - }, - { - "address": "aws_vpc.invalid", - "mode": "managed", - "type": "aws_vpc", - "name": "invalid", - "provider_name": "aws", - "schema_version": 1, - "values": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": { - "Name": "12345" - } - } - }, - { - "address": "aws_vpc.untagged", - "mode": "managed", - "type": "aws_vpc", - "name": "untagged", - "provider_name": "aws", - "schema_version": 1, - "values": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": null - } - }, - { - "address": "aws_vpc.valid", - "mode": "managed", - "type": "aws_vpc", - "name": "valid", - "provider_name": "aws", - "schema_version": 1, - "values": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": { - "Name": "123456" - } - } - } - ] - } - }, - "resource_changes": [ - { - "address": "aws_s3_bucket.invalid", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "invalid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "acl": "private", - "bucket": "my-tf-test-bucket", - "bucket_prefix": null, - "cors_rule": [], - "force_destroy": false, - "grant": [], - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "policy": null, - "replication_configuration": [], - "server_side_encryption_configuration": [], - "tags": { - "Environment": "Dev", - "Name": "My bucket" - }, - "website": [] - }, - "after_unknown": { - "acceleration_status": true, - "arn": true, - "bucket_domain_name": true, - "bucket_regional_domain_name": true, - "cors_rule": [], - "grant": [], - "hosted_zone_id": true, - "id": true, - "lifecycle_rule": [], - "logging": [], - "object_lock_configuration": [], - "region": true, - "replication_configuration": [], - "request_payer": true, - "server_side_encryption_configuration": [], - "tags": {}, - "versioning": true, - "website": [], - "website_domain": true, - "website_endpoint": true - } - } - }, - { - "address": "aws_vpc.invalid", - "mode": "managed", - "type": "aws_vpc", - "name": "invalid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": { - "Name": "12345" - } - }, - "after_unknown": { - "arn": true, - "default_network_acl_id": true, - "default_route_table_id": true, - "default_security_group_id": true, - "dhcp_options_id": true, - "enable_classiclink": true, - "enable_classiclink_dns_support": true, - "enable_dns_hostnames": true, - "id": true, - "ipv6_association_id": true, - "ipv6_cidr_block": true, - "main_route_table_id": true, - "owner_id": true, - "tags": {} - } - } - }, - { - "address": "aws_vpc.untagged", - "mode": "managed", - "type": "aws_vpc", - "name": "untagged", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": null - }, - "after_unknown": { - "arn": true, - "default_network_acl_id": true, - "default_route_table_id": true, - "default_security_group_id": true, - "dhcp_options_id": true, - "enable_classiclink": true, - "enable_classiclink_dns_support": true, - "enable_dns_hostnames": true, - "id": true, - "ipv6_association_id": true, - "ipv6_cidr_block": true, - "main_route_table_id": true, - "owner_id": true - } - } - }, - { - "address": "aws_vpc.valid", - "mode": "managed", - "type": "aws_vpc", - "name": "valid", - "provider_name": "aws", - "change": { - "actions": [ - "create" - ], - "before": null, - "after": { - "assign_generated_ipv6_cidr_block": false, - "cidr_block": "10.0.0.0/16", - "enable_dns_support": true, - "instance_tenancy": "default", - "tags": { - "Name": "123456" - } - }, - "after_unknown": { - "arn": true, - "default_network_acl_id": true, - "default_route_table_id": true, - "default_security_group_id": true, - "dhcp_options_id": true, - "enable_classiclink": true, - "enable_classiclink_dns_support": true, - "enable_dns_hostnames": true, - "id": true, - "ipv6_association_id": true, - "ipv6_cidr_block": true, - "main_route_table_id": true, - "owner_id": true, - "tags": {} - } - } - } - ], - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-1" - } - } - } - }, - "root_module": { - "resources": [ - { - "address": "aws_s3_bucket.invalid", - "mode": "managed", - "type": "aws_s3_bucket", - "name": "invalid", - "provider_config_key": "aws", - "expressions": { - "acl": { - "constant_value": "private" - }, - "bucket": { - "constant_value": "my-tf-test-bucket" - }, - "tags": { - "constant_value": { - "Environment": "Dev", - "Name": "My bucket" - } - } - }, - "schema_version": 0 - }, - { - "address": "aws_vpc.invalid", - "mode": "managed", - "type": "aws_vpc", - "name": "invalid", - "provider_config_key": "aws", - "expressions": { - "cidr_block": { - "constant_value": "10.0.0.0/16" - }, - "tags": { - "constant_value": { - "Name": "12345" - } - } - }, - "schema_version": 1 - }, - { - "address": "aws_vpc.untagged", - "mode": "managed", - "type": "aws_vpc", - "name": "untagged", - "provider_config_key": "aws", - "expressions": { - "cidr_block": { - "constant_value": "10.0.0.0/16" - } - }, - "schema_version": 1 - }, - { - "address": "aws_vpc.valid", - "mode": "managed", - "type": "aws_vpc", - "name": "valid", - "provider_config_key": "aws", - "expressions": { - "cidr_block": { - "constant_value": "10.0.0.0/16" - }, - "tags": { - "constant_value": { - "Name": "123456" - } - } - }, - "schema_version": 1 - } - ] - } - } -} + +import data.regula + +mock_config := regula_load_type("tag_all_resources_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/examples/aws/inputs/tag_all_resources_infra.tfplan b/rego/tests/examples/aws/inputs/tag_all_resources_infra.tfplan new file mode 100644 index 00000000..5d76cf13 --- /dev/null +++ b/rego/tests/examples/aws/inputs/tag_all_resources_infra.tfplan @@ -0,0 +1,364 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.invalid", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 0, + "values": { + "acl": "private", + "bucket": "my-tf-test-bucket", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": { + "Environment": "Dev", + "Name": "My bucket" + }, + "tags_all": { + "Environment": "Dev", + "Name": "My bucket" + }, + "website": [] + } + }, + { + "address": "aws_vpc.invalid", + "mode": "managed", + "type": "aws_vpc", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": { + "Name": "12345" + }, + "tags_all": { + "Name": "12345" + } + } + }, + { + "address": "aws_vpc.untagged", + "mode": "managed", + "type": "aws_vpc", + "name": "untagged", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": null + } + }, + { + "address": "aws_vpc.valid", + "mode": "managed", + "type": "aws_vpc", + "name": "valid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": { + "Name": "123456" + }, + "tags_all": { + "Name": "123456" + } + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_s3_bucket.invalid", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "acl": "private", + "bucket": "my-tf-test-bucket", + "bucket_prefix": null, + "cors_rule": [], + "force_destroy": false, + "grant": [], + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "policy": null, + "replication_configuration": [], + "server_side_encryption_configuration": [], + "tags": { + "Environment": "Dev", + "Name": "My bucket" + }, + "tags_all": { + "Environment": "Dev", + "Name": "My bucket" + }, + "website": [] + }, + "after_unknown": { + "acceleration_status": true, + "arn": true, + "bucket_domain_name": true, + "bucket_regional_domain_name": true, + "cors_rule": [], + "grant": [], + "hosted_zone_id": true, + "id": true, + "lifecycle_rule": [], + "logging": [], + "object_lock_configuration": [], + "region": true, + "replication_configuration": [], + "request_payer": true, + "server_side_encryption_configuration": [], + "tags": {}, + "tags_all": {}, + "versioning": true, + "website": [], + "website_domain": true, + "website_endpoint": true + } + } + }, + { + "address": "aws_vpc.invalid", + "mode": "managed", + "type": "aws_vpc", + "name": "invalid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": { + "Name": "12345" + }, + "tags_all": { + "Name": "12345" + } + }, + "after_unknown": { + "arn": true, + "default_network_acl_id": true, + "default_route_table_id": true, + "default_security_group_id": true, + "dhcp_options_id": true, + "enable_classiclink": true, + "enable_classiclink_dns_support": true, + "enable_dns_hostnames": true, + "id": true, + "ipv6_association_id": true, + "ipv6_cidr_block": true, + "main_route_table_id": true, + "owner_id": true, + "tags": {}, + "tags_all": {} + } + } + }, + { + "address": "aws_vpc.untagged", + "mode": "managed", + "type": "aws_vpc", + "name": "untagged", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": null + }, + "after_unknown": { + "arn": true, + "default_network_acl_id": true, + "default_route_table_id": true, + "default_security_group_id": true, + "dhcp_options_id": true, + "enable_classiclink": true, + "enable_classiclink_dns_support": true, + "enable_dns_hostnames": true, + "id": true, + "ipv6_association_id": true, + "ipv6_cidr_block": true, + "main_route_table_id": true, + "owner_id": true, + "tags_all": true + } + } + }, + { + "address": "aws_vpc.valid", + "mode": "managed", + "type": "aws_vpc", + "name": "valid", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "assign_generated_ipv6_cidr_block": false, + "cidr_block": "10.0.0.0/16", + "enable_dns_support": true, + "instance_tenancy": "default", + "tags": { + "Name": "123456" + }, + "tags_all": { + "Name": "123456" + } + }, + "after_unknown": { + "arn": true, + "default_network_acl_id": true, + "default_route_table_id": true, + "default_security_group_id": true, + "dhcp_options_id": true, + "enable_classiclink": true, + "enable_classiclink_dns_support": true, + "enable_dns_hostnames": true, + "id": true, + "ipv6_association_id": true, + "ipv6_cidr_block": true, + "main_route_table_id": true, + "owner_id": true, + "tags": {}, + "tags_all": {} + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_s3_bucket.invalid", + "mode": "managed", + "type": "aws_s3_bucket", + "name": "invalid", + "provider_config_key": "aws", + "expressions": { + "acl": { + "constant_value": "private" + }, + "bucket": { + "constant_value": "my-tf-test-bucket" + }, + "tags": { + "constant_value": { + "Environment": "Dev", + "Name": "My bucket" + } + } + }, + "schema_version": 0 + }, + { + "address": "aws_vpc.invalid", + "mode": "managed", + "type": "aws_vpc", + "name": "invalid", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "constant_value": "10.0.0.0/16" + }, + "tags": { + "constant_value": { + "Name": "12345" + } + } + }, + "schema_version": 1 + }, + { + "address": "aws_vpc.untagged", + "mode": "managed", + "type": "aws_vpc", + "name": "untagged", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "constant_value": "10.0.0.0/16" + } + }, + "schema_version": 1 + }, + { + "address": "aws_vpc.valid", + "mode": "managed", + "type": "aws_vpc", + "name": "valid", + "provider_config_key": "aws", + "expressions": { + "cidr_block": { + "constant_value": "10.0.0.0/16" + }, + "tags": { + "constant_value": { + "Name": "123456" + } + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/rego/tests/examples/aws/inputs/useast1_only_infra.rego b/rego/tests/examples/aws/inputs/useast1_only_infra.rego index bb19f5d9..ef1d76ba 100644 --- a/rego/tests/examples/aws/inputs/useast1_only_infra.rego +++ b/rego/tests/examples/aws/inputs/useast1_only_infra.rego @@ -1,4 +1,4 @@ -# Copyright 2020 Fugue, Inc. +# Copyright 2020-2021 Fugue, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,36 +16,17 @@ # # tests/examples/aws/inputs/useast1_only_infra.tf # -# using `generate_test_inputs.sh` and should not be modified +# using 'generate_test_inputs.sh' and should not be modified # directly. # # It provides three inputs for testing: # - mock_input: The resource view input as passed to advanced rules # - mock_resources: The resources present as a convenience for tests -# - mock_plan_input: The original plan input as generated by terraform +# - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.useast1_only_infra -import data.fugue.resource_view.resource_view_input -mock_input = ret { - ret = resource_view_input with input as mock_plan_input -} -mock_resources = mock_input.resources -mock_plan_input = { - "format_version": "0.1", - "terraform_version": "0.12.18", - "planned_values": { - "root_module": {} - }, - "configuration": { - "provider_config": { - "aws": { - "name": "aws", - "expressions": { - "region": { - "constant_value": "us-east-1" - } - } - } - }, - "root_module": {} - } -} + +import data.regula + +mock_config := regula_load_type("useast1_only_infra.tfplan", "tf-plan") +mock_input := regula.mock_input(mock_config) +mock_resources := mock_input.resources diff --git a/rego/tests/examples/aws/inputs/useast1_only_infra.tfplan b/rego/tests/examples/aws/inputs/useast1_only_infra.tfplan new file mode 100644 index 00000000..52581492 --- /dev/null +++ b/rego/tests/examples/aws/inputs/useast1_only_infra.tfplan @@ -0,0 +1,20 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": {} + }, + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": {} + } +} diff --git a/rego/tests/lib/fugue_resource_view_test.rego b/rego/tests/lib/fugue_resource_view_test.rego index f3a22539..9617f74c 100644 --- a/rego/tests/lib/fugue_resource_view_test.rego +++ b/rego/tests/lib/fugue_resource_view_test.rego @@ -60,10 +60,10 @@ test_mock_resource_view { } mock_resource_view = ret { - ret = resource_view with input as mock_plan_input + ret = resource_view with input as mock_config } -mock_plan_input = { +mock_config = { "format_version":"0.1", "terraform_version":"0.12.18", "planned_values":{ From f6f83e0c585bb1cce334ac7118137c944e3b214a Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 4 May 2021 15:38:16 -0400 Subject: [PATCH 36/65] [RM-5352] Replace git2go with go-git --- go.mod | 8 +++--- go.sum | 58 +++++++++++++++++++++++++++++++---------- pkg/git/repo.go | 51 ++++++++++++++++++++++++------------ pkg/loader/input.go | 2 +- pkg/loader/loadpaths.go | 2 +- 5 files changed, 84 insertions(+), 37 deletions(-) diff --git a/go.mod b/go.mod index 77cfe0c8..12991b46 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,13 @@ go 1.16 require ( github.com/alexeyco/simpletable v1.0.0 github.com/fatih/color v1.7.0 + github.com/go-git/go-billy/v5 v5.3.1 + github.com/go-git/go-git/v5 v5.3.0 github.com/golang/mock v1.5.0 - github.com/kr/text v0.2.0 // indirect - github.com/libgit2/git2go/v31 v31.4.14 - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/open-policy-agent/opa v0.26.0 github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.4.0 github.com/thediveo/enumflag v0.10.1 - golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) diff --git a/go.sum b/go.sum index d1f774cb..36cc6831 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -21,6 +23,7 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -28,12 +31,14 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexeyco/simpletable v1.0.0 h1:ZQ+LvJ4bmoeHb+dclF64d0LX+7QAi7awsfCrptZrpHk= github.com/alexeyco/simpletable v1.0.0/go.mod h1:VJWVTtGUnW7EKbMRH8cE13SigKGx/1fO2SeeOiGeBkk= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -76,17 +81,29 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= +github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -133,8 +150,6 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -176,9 +191,13 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -190,6 +209,7 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -197,12 +217,12 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/libgit2/git2go/v31 v31.4.14 h1:6GOd3965D9e/+gjxCwZF4eQ+vB9kKB4yKFqdQr6XZ2E= -github.com/libgit2/git2go/v31 v31.4.14/go.mod h1:c/rkJcBcUFx6wHaT++UwNpKvIsmPNqCeQ/vzO4DrEec= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= @@ -240,7 +260,6 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= @@ -323,8 +342,10 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -366,6 +387,7 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/wasmerio/go-ext-wasm v0.3.1 h1:G95XP3fE2FszQSwIU+fHPBYzD0Csmd2ef33snQXNA5Q= github.com/wasmerio/go-ext-wasm v0.3.1/go.mod h1:VGyarTzasuS7k5KhSIGpM3tciSZlkP31Mp9VJTHMMeI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= @@ -389,14 +411,15 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c h1:9HhBz5L/UjnK9XLtiZhYAdue5BVKep3PMmS2LuPDt8k= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -439,8 +462,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200927032502-5d4f70055728 h1:5wtQIAulKU5AbLQOkjxl32UufnIOqgBX72pS0AV14H0= golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -469,18 +494,21 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88 h1:KmZPnMocC93w341XZp26yTJg8Za7lhb2KhkYmixoeso= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0JU9kksj8+F/bnQUVLGDM= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -561,8 +589,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -572,6 +601,7 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/git/repo.go b/pkg/git/repo.go index 6dd41715..b97b373b 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -15,34 +15,54 @@ package git import ( + "fmt" "os" "path/filepath" + "strings" - git "github.com/libgit2/git2go/v31" + "github.com/go-git/go-billy/v5/osfs" + "github.com/go-git/go-git/v5/plumbing/format/gitignore" ) +var defaultGitIgnorePatterns = []gitignore.Pattern{ + gitignore.ParsePattern("..", []string{}), + gitignore.ParsePattern(".git", []string{}), +} + type Repo interface { - IsPathIgnored(path string) bool + IsPathIgnored(path string, isDir bool) bool } type repo struct { - path string - repo *git.Repository + path string + ignoreMatcher gitignore.Matcher +} + +func NewRepo(path string) (Repo, error) { + fsys := osfs.New(path) + patterns, err := gitignore.ReadPatterns(fsys, nil) + if err != nil { + return nil, err + } + patterns = append(patterns, defaultGitIgnorePatterns...) + return &repo{ + path: path, + ignoreMatcher: gitignore.NewMatcher(patterns), + }, nil } -func (r *repo) IsPathIgnored(path string) bool { +func (r *repo) IsPathIgnored(path string, isDir bool) bool { + fmt.Println("Got path", path) absPath, err := filepath.Abs(path) if err != nil { return false } - // git2go's IsPathIgnored results differ from git check-ignore if you've got a - // pattern like we do where the path matches the name of the root directory for - // the repository. It's safe to assume that this should always be false. - if absPath == r.path { + relPath, err := filepath.Rel(r.path, absPath) + if err != nil { return false } - ignored, _ := r.repo.IsPathIgnored(absPath) - return ignored + splitPath := strings.Split(relPath, string(os.PathSeparator)) + return r.ignoreMatcher.Match(splitPath, isDir) } // RepoFinder finds the git repository for a given directory. @@ -61,6 +81,7 @@ func NewRepoFinder() *RepoFinder { // It works by searching within the given directory, followed by searching in parent // directories until it either reaches the top-level directory or encounters an error. func (s *RepoFinder) FindRepo(path string) Repo { + fmt.Println("In find repo", path) absPath, err := filepath.Abs(path) if err != nil { return nil @@ -80,15 +101,13 @@ func (s *RepoFinder) FindRepo(path string) Repo { } for _, e := range entries { if e.Name() == ".git" { - r, err := git.OpenRepository(filepath.Join(absPath, e.Name())) + fmt.Println("Found repo", absPath) + r, err := NewRepo(absPath) if err != nil { s.cache[absPath] = nil return nil } - s.cache[absPath] = &repo{ - path: absPath, - repo: r, - } + s.cache[absPath] = r return s.cache[absPath] } } diff --git a/pkg/loader/input.go b/pkg/loader/input.go index adbca9bc..f5b65b84 100644 --- a/pkg/loader/input.go +++ b/pkg/loader/input.go @@ -70,7 +70,7 @@ func newDirectory(opts directoryOptions) (InputDirectory, error) { n := e.Name() p := filepath.Join(opts.Path, n) if repo != nil { - if ignored := repo.IsPathIgnored(p); ignored { + if ignored := repo.IsPathIgnored(p, e.IsDir()); ignored { continue } } diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 7f7f2d64..c358dfbd 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -82,7 +82,7 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { noIgnore := options.NoIgnore if !noIgnore { if repo := gitRepoFinder.FindRepo(path); repo != nil { - noIgnore = repo.IsPathIgnored(path) + noIgnore = repo.IsPathIgnored(path, true) } } i, err := newDirectory(directoryOptions{ From 44388b51f5df9aa1b24592f366a03e5a77a34f5d Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 4 May 2021 16:48:35 -0400 Subject: [PATCH 37/65] [RM-5352] Apply gitignore while finding gitignores --- pkg/git/repo.go | 67 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/pkg/git/repo.go b/pkg/git/repo.go index b97b373b..337d416e 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -15,11 +15,12 @@ package git import ( - "fmt" + "bufio" "os" "path/filepath" "strings" + "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/plumbing/format/gitignore" ) @@ -40,7 +41,7 @@ type repo struct { func NewRepo(path string) (Repo, error) { fsys := osfs.New(path) - patterns, err := gitignore.ReadPatterns(fsys, nil) + patterns, err := ReadPatterns(fsys, nil, nil) if err != nil { return nil, err } @@ -52,7 +53,6 @@ func NewRepo(path string) (Repo, error) { } func (r *repo) IsPathIgnored(path string, isDir bool) bool { - fmt.Println("Got path", path) absPath, err := filepath.Abs(path) if err != nil { return false @@ -81,7 +81,6 @@ func NewRepoFinder() *RepoFinder { // It works by searching within the given directory, followed by searching in parent // directories until it either reaches the top-level directory or encounters an error. func (s *RepoFinder) FindRepo(path string) Repo { - fmt.Println("In find repo", path) absPath, err := filepath.Abs(path) if err != nil { return nil @@ -101,7 +100,6 @@ func (s *RepoFinder) FindRepo(path string) Repo { } for _, e := range entries { if e.Name() == ".git" { - fmt.Println("Found repo", absPath) r, err := NewRepo(absPath) if err != nil { s.cache[absPath] = nil @@ -123,3 +121,62 @@ func (s *RepoFinder) FindRepo(path string) Repo { } return nil } + +// Vendored from go-git so that we can fix their behavior +// readIgnoreFile reads a specific git ignore file. +func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []gitignore.Pattern, err error) { + f, err := fs.Open(fs.Join(append(path, ignoreFile)...)) + if err == nil { + defer f.Close() + + scanner := bufio.NewScanner(f) + for scanner.Scan() { + s := scanner.Text() + if !strings.HasPrefix(s, "#") && len(strings.TrimSpace(s)) > 0 { + ps = append(ps, gitignore.ParsePattern(s, path)) + } + } + } else if !os.IsNotExist(err) { + return nil, err + } + + return +} + +// ReadPatterns reads gitignore patterns recursively traversing through the directory +// structure. The result is in the ascending order of priority (last higher). This +// function has been modified to respect gitignore patterns while it's traversing. This +// has a big impact for larger repositories. +func ReadPatterns(fs billy.Filesystem, path []string, accumulator []gitignore.Pattern) ([]gitignore.Pattern, error) { + ps, _ := readIgnoreFile(fs, path, ".gitignore") + accumulator = append(accumulator, ps...) + + var fis []os.FileInfo + fis, err := fs.ReadDir(fs.Join(path...)) + if err != nil { + return nil, err + } + + matcher := gitignore.NewMatcher(accumulator) + for _, fi := range fis { + name := fi.Name() + subPath := append(path, name) + isDir := fi.IsDir() + if matcher.Match(subPath, isDir) { + continue + } + if isDir && name != ".git" { + var subps []gitignore.Pattern + subps, err = ReadPatterns(fs, subPath, accumulator) + if err != nil { + return nil, err + } + + if len(subps) > 0 { + ps = append(ps, subps...) + } + } + } + + return ps, nil +} From 417b8e9869f91cf6476f060ab9dc6a413ed9fc62 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 5 May 2021 14:27:47 -0400 Subject: [PATCH 38/65] [RM-5352] Preparing for release --- .github/workflows/main.yml | 12 ++- .gitignore | 1 + Dockerfile | 42 +++------ Makefile | 20 ++++- bin/regula | 171 ------------------------------------- requirements.txt | 1 - 6 files changed, 34 insertions(+), 213 deletions(-) delete mode 100755 bin/regula delete mode 100644 requirements.txt diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 40032338..58a230a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,13 +3,11 @@ on: [pull_request, push] jobs: test: runs-on: ubuntu-latest - name: OPA test + name: Test steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 - - run: | - go get github.com/open-policy-agent/opa - go install github.com/open-policy-agent/opa - - run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH - - run: opa test lib rules examples tests - - run: ./scripts/check-naming.sh + with: + go-version: '^1.16' + - run: make test + - run: cd rego && ./scripts/check-naming.sh diff --git a/.gitignore b/.gitignore index 42bef8e7..e149a95c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ regula-* .vscode/ .scratch/ .regula-history +build/ diff --git a/Dockerfile b/Dockerfile index 5f464b3a..68a922c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,11 @@ -FROM python:3.9.2-alpine3.13 - -# We need bash for the main regula script since it uses arrays. -# We need git to support terraform modules -RUN apk add --update bash git && rm -rf /var/cache/apk/* - -# Install OPA. -ARG OPA_VERSION=0.26.0 -RUN wget -O '/usr/local/bin/opa' \ - "https://github.com/open-policy-agent/opa/releases/download/v${OPA_VERSION}/opa_linux_amd64" &&\ - chmod +x '/usr/local/bin/opa' - -# Install terraform. -ARG TERRAFORM_VERSION=0.14.7 -ENV TF_IN_AUTOMATION=true -RUN wget -O "/tmp/terraform-${TERRAFORM_VERSION}.zip" \ - "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" && \ - unzip -d '/usr/local/bin' "/tmp/terraform-${TERRAFORM_VERSION}.zip" &&\ - rm "/tmp/terraform-${TERRAFORM_VERSION}.zip" - -# Install cfn-flip -ARG CFNFLIP_VERSION=1.2.3 -RUN pip install "cfn-flip==${CFNFLIP_VERSION}" - -# Update regula files -RUN mkdir -p /opt/regula -COPY lib /opt/regula/lib -COPY rules /opt/regula/rules -COPY bin/regula /usr/local/bin - -ENTRYPOINT ["regula", "-d", "/opt/regula"] +FROM golang:1.16-alpine as builder +ARG version +ARG gitcommit +WORKDIR /build +COPY . . +ENV ldflags "-X \"github.com/fugue/regula/cmd.Version=${version}\" -X \"github.com/fugue/regula/cmd.GitCommit=${gitcommit}\"" +RUN go build -ldflags="${ldflags} -s -w" + +FROM alpine:latest +COPY --from=builder /build/regula /usr/local/bin +ENTRYPOINT [ "regula" ] diff --git a/Makefile b/Makefile index c140983e..a38668ef 100644 --- a/Makefile +++ b/Makefile @@ -75,6 +75,20 @@ lint: $(GOLINT) ./... go vet ./... -.PHONY: printsrc -printsrc: - @echo $(CLI_SOURCE) +.PHONY: docker +docker: $(COPIED_REGO_LIB) $(COPIED_REGO_RULES) + rm -rf build + mkdir -p build + cp -R pkg build + cp -R cmd build + cp go.mod build + cp go.sum build + cp main.go build + cp Dockerfile build + cd build + docker build \ + --build-arg version=$(VERSION) \ + --build-arg gitcommit=$(GITCOMMIT) \ + --tag fugue/regula:$(VERSION) \ + --tag fugue/regula:latest \ + . diff --git a/bin/regula b/bin/regula deleted file mode 100755 index ac670b9b..00000000 --- a/bin/regula +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2020 Fugue, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -o nounset -o errexit -o pipefail - -# Basic command line argument handling. -if [[ $# -lt 1 || "$1" == "-h" ]]; then - 1>&2 echo "Regula is a little wrapper to run Rego validations on terraform" - 1>&2 echo "files. It is meant to be used as a pre-flight check before" - 1>&2 echo "deployment." - 1>&2 echo "" - 1>&2 echo "Usage: $0 [-d REGO_PATH] [INPUT_PATH]" - 1>&2 echo "" - 1>&2 echo "For example $0 -d /opt/regula/rules -d /opt/regula/lib infra_tf/" - 1>&2 echo "" - 1>&2 echo "INPUT_PATH can be a terraform directory, a terraform plan in" - 1>&2 echo "JSON format or a cloudformation template." - exit 1 -fi - -INPUT_PATHS=() -D_REGO_PATHS=() -if [[ "$1" == "-d" ]]; then - # New way of parsing arguments: multiple -d rego paths followed by input - # files. - while [[ "$1" == "-d" ]]; do - D_REGO_PATHS+=("-d") - D_REGO_PATHS+=("$2") - shift 2 - done - INPUT_PATHS=("$@") -else - # Old way of parsing arguments: single input followed by multiple rego paths. - INPUT_PATHS+=("$1") - shift 1 - REGO_PATHS=("$@") - - # Prepend `-d` to every argument because `opa` expects to see many `-d` - for p in "${REGO_PATHS[@]}"; do - D_REGO_PATHS+=('-d') - D_REGO_PATHS+=("$p") - done -fi - -function detect_input_type { - if [[ -d "$1" ]]; then - echo "terraform_dir" - return - fi - - opa eval "${D_REGO_PATHS[@]}" -i "$1" --format pretty \ - 'data.fugue.input_type.input_type' | \ - tr -d '"' # Unquote the string, not great but avoids jq dependency. -} - -# Setting this variable will cause terraform to print a little less information -# on what to do next. -export TF_IN_AUTOMATION=true - -# Allow overriding terraform version. -TERRAFORM="${TERRAFORM:-terraform}" - -# Hide the output of a command only if it succeeds. -function silently { - local log - log="$(mktemp -t silently.XXXXXXX)" - local exit_code - exit_code="" - 1>&2 echo "${1+$@}" - ${1+"$@"} >"$log" 2>&1 || exit_code=$? - if [[ -n "$exit_code" ]]; then - 1>&2 echo "${1+$@} failed; output ($log):" - 1>&2 cat "$log" - exit $exit_code - fi - rm "$log" -} - -# Prepare a function that will clean up all termporary files, that we'll store -# in an array. -CLEANUP_PATHS=() -function cleanup { - rm -f "${CLEANUP_PATHS[@]}" -} -trap cleanup exit - -# Preprocessing happens per input file. -PROCESSED_INPUT_PATHS=() -for INPUT_PATH in "${INPUT_PATHS[@]}"; do - # Capture stdin. - if [[ "$INPUT_PATH" == "-" ]]; then - INPUT_PATH="$(mktemp -t input.XXXXXXX)" - CLEANUP_PATHS+=("$INPUT_PATH") - cat - >"$INPUT_PATH" - fi - - # Determine input type. - INPUT_TYPE="$(detect_input_type "$INPUT_PATH")" - - if [[ "$INPUT_TYPE" == "terraform_dir" ]]; then - # Temporary files. - TERRAFORM_PLAN="$(mktemp -t plan.XXXXXXX)" - CLEANUP_PATHS+=("$TERRAFORM_PLAN") - TERRAFORM_PLAN_JSON="$TERRAFORM_PLAN.json" - # Run terraform to obtain the plan. - (cd "$INPUT_PATH" && - silently "$TERRAFORM" init && - silently "$TERRAFORM" plan -refresh=false -out="$TERRAFORM_PLAN" && - "$TERRAFORM" show -json "$TERRAFORM_PLAN" >"$TERRAFORM_PLAN_JSON") - INPUT_PATH="$TERRAFORM_PLAN_JSON" - elif [[ "$INPUT_TYPE" == "cloudformation" ]]; then - CFN_JSON="$(mktemp -t cfn.XXXXXXX)" - CLEANUP_PATHS+=("$CFN_JSON") - cfn-flip -j "$INPUT_PATH" >"$CFN_JSON" - INPUT_PATH="$CFN_JSON" - fi - PROCESSED_INPUT_PATHS+=("$INPUT_PATH") -done - -# We want Rego to have access to the filenames of whatever we are checking. -# So we merge them into a single JSON file. This is an array with the shape: -# -# [ -# { -# "filepath": "1.json", -# "content": {...} -# }, -# { -# "filepath": "2.json", -# "content": {...} -# } -# ] -# -# This works because we know that all the processed input paths are valid JSON. -INPUT_PATH="$(mktemp -t merged.XXXXXXX)" -CLEANUP_PATHS+=("$INPUT_PATH") -echo "[" >"$INPUT_PATH" -for i in "${!PROCESSED_INPUT_PATHS[@]}"; do - filepath="${INPUT_PATHS[$i]}" - echo "{\"filepath\":\"$filepath\",\"content\":" >>"$INPUT_PATH" - cat "${PROCESSED_INPUT_PATHS[$i]}" >>"$INPUT_PATH" - if [[ $(($i + 1)) == "${#PROCESSED_INPUT_PATHS[@]}" ]]; then - echo "}]" >>"$INPUT_PATH" - else - echo "}," >>"$INPUT_PATH" - fi -done - -# Finally, run OPA on the result to get out our report. -OUTPUT_PATH="$(mktemp -t output.XXXXXXX)" -CLEANUP_PATHS+=("$OUTPUT_PATH") -opa eval --format=pretty --input "$INPUT_PATH" \ - "${D_REGO_PATHS[@]}" \ - 'data.fugue.regula.report' >"$OUTPUT_PATH" -cat "$OUTPUT_PATH" -NUM_FAILED="$(opa eval --format=pretty --input "$OUTPUT_PATH" \ - "input.summary.rule_results.FAIL")" -if [[ "$NUM_FAILED" != "0" ]]; then - exit 1 -fi diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c4ff8e66..00000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -cfn-flip==1.2.3 From bc3d33ad8248d5e56d4f4318037ffb71a5934493 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 5 May 2021 13:49:42 -0400 Subject: [PATCH 39/65] [RM-5352] Started on a gitignore optimization --- pkg/git/input_tree.go | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 pkg/git/input_tree.go diff --git a/pkg/git/input_tree.go b/pkg/git/input_tree.go new file mode 100644 index 00000000..6f0bc507 --- /dev/null +++ b/pkg/git/input_tree.go @@ -0,0 +1,59 @@ +package git + +import ( + "os" + "strings" +) + +type TreeNode struct { + Children map[string]*TreeNode +} + +func NewTreeNode(splitPath []string) *TreeNode { + t := &TreeNode{ + Children: map[string]*TreeNode{}, + } + if len(splitPath) > 0 { + t.Children[splitPath[0]] = NewTreeNode(splitPath[1:]) + } + return t +} + +func (t *TreeNode) Contains(splitPath []string) bool { + if pathLen := len(splitPath); pathLen < 1 { + return true + } else { + if child, ok := t.Children[splitPath[1]]; ok { + return child.Contains(splitPath[1:]) + } else { + return false + } + } +} + +func NewTree(paths []string) *TreeNode { + rootNode := NewTreeNode(nil) + for _, path := range paths { + splitPath := strings.Split(path, string(os.PathSeparator)) + if len(splitPath) < 1 { + continue + } + rootNode.Children[splitPath[0]] = NewTreeNode(splitPath[1:]) + } + + return rootNode + + // if len(splitPath) < 1 { + // return nil, fmt.Errorf("Got an empty path when initializing input tree") + // } + // rootNode := NewTreeNode() + // currChild := NewTreeNode() + // rootNode.Children[splitPath[0]] = currChild + // for len(splitPath) > 1 { + // splitPath = splitPath[1:] + // nextChild := NewTreeNode() + // currChild.Children[splitPath[0]] = nextChild + // currChild = nextChild + // } + // return +} From 90dc0ab6c52ec308c5fd6be300d7b840654b514d Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 5 May 2021 15:51:29 -0400 Subject: [PATCH 40/65] [RM-5352] Ignore non-input paths in gitignore search --- go.mod | 2 +- pkg/git/input_tree.go | 75 ++++++++++++++++++++++++----------------- pkg/git/repo.go | 42 ++++++++++++++--------- pkg/loader/loadpaths.go | 2 +- 4 files changed, 72 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 12991b46..aeb127de 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/alexeyco/simpletable v1.0.0 github.com/fatih/color v1.7.0 - github.com/go-git/go-billy/v5 v5.3.1 + github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-git/go-git/v5 v5.3.0 github.com/golang/mock v1.5.0 github.com/open-policy-agent/opa v0.26.0 diff --git a/pkg/git/input_tree.go b/pkg/git/input_tree.go index 6f0bc507..5e53e540 100644 --- a/pkg/git/input_tree.go +++ b/pkg/git/input_tree.go @@ -2,58 +2,71 @@ package git import ( "os" + "path/filepath" "strings" ) -type TreeNode struct { - Children map[string]*TreeNode +type Relation int + +const ( + None Relation = iota + IsParent + IsChild +) + +type InputTreeNode struct { + Children map[string]*InputTreeNode } -func NewTreeNode(splitPath []string) *TreeNode { - t := &TreeNode{ - Children: map[string]*TreeNode{}, +func NewInputTreeNode(splitPath []string) *InputTreeNode { + t := &InputTreeNode{ + Children: map[string]*InputTreeNode{}, } if len(splitPath) > 0 { - t.Children[splitPath[0]] = NewTreeNode(splitPath[1:]) + t.Children[splitPath[0]] = NewInputTreeNode(splitPath[1:]) } return t } -func (t *TreeNode) Contains(splitPath []string) bool { +func (t *InputTreeNode) Relation(splitPath []string) Relation { if pathLen := len(splitPath); pathLen < 1 { - return true + // In this case the splitPath is a parent of the tree node + return IsParent } else { + if len(t.Children) == 0 { + // In this case the tree node is a parent of the split path + return IsChild + } if child, ok := t.Children[splitPath[1]]; ok { - return child.Contains(splitPath[1:]) - } else { - return false + return child.Relation(splitPath[1:]) } + + return None } } -func NewTree(paths []string) *TreeNode { - rootNode := NewTreeNode(nil) +func (t *InputTreeNode) AddChild(splitPath []string) { + if len(splitPath) < 1 { + return + } + if child, ok := t.Children[splitPath[0]]; ok { + child.AddChild(splitPath[1:]) + return + } + t.Children[splitPath[0]] = NewInputTreeNode(splitPath[1:]) +} + +func NewInputTree(paths []string) *InputTreeNode { + rootNode := NewInputTreeNode(nil) for _, path := range paths { - splitPath := strings.Split(path, string(os.PathSeparator)) - if len(splitPath) < 1 { - continue + absPath, err := filepath.Abs(path) + if err != nil { + // This case can happen for the stdin path "-" + absPath = path } - rootNode.Children[splitPath[0]] = NewTreeNode(splitPath[1:]) + splitPath := strings.Split(absPath, string(os.PathSeparator)) + rootNode.AddChild(splitPath) } return rootNode - - // if len(splitPath) < 1 { - // return nil, fmt.Errorf("Got an empty path when initializing input tree") - // } - // rootNode := NewTreeNode() - // currChild := NewTreeNode() - // rootNode.Children[splitPath[0]] = currChild - // for len(splitPath) > 1 { - // splitPath = splitPath[1:] - // nextChild := NewTreeNode() - // currChild.Children[splitPath[0]] = nextChild - // currChild = nextChild - // } - // return } diff --git a/pkg/git/repo.go b/pkg/git/repo.go index 337d416e..4ba15f81 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -20,8 +20,6 @@ import ( "path/filepath" "strings" - "github.com/go-git/go-billy/v5" - "github.com/go-git/go-billy/v5/osfs" "github.com/go-git/go-git/v5/plumbing/format/gitignore" ) @@ -39,9 +37,9 @@ type repo struct { ignoreMatcher gitignore.Matcher } -func NewRepo(path string) (Repo, error) { - fsys := osfs.New(path) - patterns, err := ReadPatterns(fsys, nil, nil) +func NewRepo(path string, inputTree *InputTreeNode) (Repo, error) { + // fsys := osfs.New(path) + patterns, err := ReadPatterns(strings.Split(path, string(os.PathSeparator)), inputTree, nil, nil, None) if err != nil { return nil, err } @@ -67,13 +65,16 @@ func (r *repo) IsPathIgnored(path string, isDir bool) bool { // RepoFinder finds the git repository for a given directory. type RepoFinder struct { - cache map[string]Repo + inputTree *InputTreeNode + cache map[string]Repo } // NewRepoFinder returns a new RepoFinder instance -func NewRepoFinder() *RepoFinder { +func NewRepoFinder(inputPaths []string) *RepoFinder { + inputTree := NewInputTree(inputPaths) return &RepoFinder{ - cache: map[string]Repo{}, + inputTree: inputTree, + cache: map[string]Repo{}, } } @@ -100,7 +101,7 @@ func (s *RepoFinder) FindRepo(path string) Repo { } for _, e := range entries { if e.Name() == ".git" { - r, err := NewRepo(absPath) + r, err := NewRepo(absPath, s.inputTree) if err != nil { s.cache[absPath] = nil return nil @@ -124,8 +125,9 @@ func (s *RepoFinder) FindRepo(path string) Repo { // Vendored from go-git so that we can fix their behavior // readIgnoreFile reads a specific git ignore file. -func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []gitignore.Pattern, err error) { - f, err := fs.Open(fs.Join(append(path, ignoreFile)...)) +func readIgnoreFile(prefix []string, path []string, ignoreFile string) (ps []gitignore.Pattern, err error) { + absPath := append(append(prefix, path...), ignoreFile) + f, err := os.Open(filepath.Join(absPath...)) if err == nil { defer f.Close() @@ -147,12 +149,14 @@ func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps [ // structure. The result is in the ascending order of priority (last higher). This // function has been modified to respect gitignore patterns while it's traversing. This // has a big impact for larger repositories. -func ReadPatterns(fs billy.Filesystem, path []string, accumulator []gitignore.Pattern) ([]gitignore.Pattern, error) { - ps, _ := readIgnoreFile(fs, path, ".gitignore") +func ReadPatterns(prefix []string, inputTree *InputTreeNode, path []string, accumulator []gitignore.Pattern, lastRelation Relation) ([]gitignore.Pattern, error) { + ps, _ := readIgnoreFile(prefix, path, ".gitignore") accumulator = append(accumulator, ps...) - var fis []os.FileInfo - fis, err := fs.ReadDir(fs.Join(path...)) + absPath := append(prefix, path...) + joinPath := filepath.Join(absPath...) + var fis []os.DirEntry + fis, err := os.ReadDir(joinPath) if err != nil { return nil, err } @@ -166,8 +170,14 @@ func ReadPatterns(fs billy.Filesystem, path []string, accumulator []gitignore.Pa continue } if isDir && name != ".git" { + if lastRelation != IsParent { + lastRelation = inputTree.Relation(append(absPath, name)) + } + if lastRelation == None { + continue + } var subps []gitignore.Pattern - subps, err = ReadPatterns(fs, subPath, accumulator) + subps, err = ReadPatterns(prefix, inputTree, subPath, accumulator, lastRelation) if err != nil { return nil, err } diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index c358dfbd..504e7116 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -48,7 +48,7 @@ func LoadPaths(options LoadPathsOptions) (LoadedConfigurations, error) { } return nil } - gitRepoFinder := git.NewRepoFinder() + gitRepoFinder := git.NewRepoFinder(options.Paths) for _, path := range options.Paths { if path == "-" { path = stdIn From 2f5acf3982c31e0b5cf98cfbffe80d99a6cbbd46 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 5 May 2021 16:40:45 -0400 Subject: [PATCH 41/65] [RM-5352] Got the gitignore support feeling fast --- pkg/git/input_tree.go | 3 ++- pkg/git/repo.go | 25 +++++++++++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pkg/git/input_tree.go b/pkg/git/input_tree.go index 5e53e540..721c824d 100644 --- a/pkg/git/input_tree.go +++ b/pkg/git/input_tree.go @@ -29,6 +29,7 @@ func NewInputTreeNode(splitPath []string) *InputTreeNode { } func (t *InputTreeNode) Relation(splitPath []string) Relation { + // fmt.Println("Checking relation", strings.Join(splitPath, "/")) if pathLen := len(splitPath); pathLen < 1 { // In this case the splitPath is a parent of the tree node return IsParent @@ -37,7 +38,7 @@ func (t *InputTreeNode) Relation(splitPath []string) Relation { // In this case the tree node is a parent of the split path return IsChild } - if child, ok := t.Children[splitPath[1]]; ok { + if child, ok := t.Children[splitPath[0]]; ok { return child.Relation(splitPath[1:]) } diff --git a/pkg/git/repo.go b/pkg/git/repo.go index 4ba15f81..36eca3c8 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -39,7 +39,7 @@ type repo struct { func NewRepo(path string, inputTree *InputTreeNode) (Repo, error) { // fsys := osfs.New(path) - patterns, err := ReadPatterns(strings.Split(path, string(os.PathSeparator)), inputTree, nil, nil, None) + patterns, err := ReadPatterns(path, inputTree, nil, nil, None) if err != nil { return nil, err } @@ -125,9 +125,11 @@ func (s *RepoFinder) FindRepo(path string) Repo { // Vendored from go-git so that we can fix their behavior // readIgnoreFile reads a specific git ignore file. -func readIgnoreFile(prefix []string, path []string, ignoreFile string) (ps []gitignore.Pattern, err error) { - absPath := append(append(prefix, path...), ignoreFile) - f, err := os.Open(filepath.Join(absPath...)) +func readIgnoreFile(prefix string, path []string, ignoreFile string) (ps []gitignore.Pattern, err error) { + absPath := append(append([]string{prefix}, path...), ignoreFile) + joinPath := filepath.Join(absPath...) + // fmt.Println("Trying to read .gitignore file", joinPath) + f, err := os.Open(joinPath) if err == nil { defer f.Close() @@ -149,12 +151,13 @@ func readIgnoreFile(prefix []string, path []string, ignoreFile string) (ps []git // structure. The result is in the ascending order of priority (last higher). This // function has been modified to respect gitignore patterns while it's traversing. This // has a big impact for larger repositories. -func ReadPatterns(prefix []string, inputTree *InputTreeNode, path []string, accumulator []gitignore.Pattern, lastRelation Relation) ([]gitignore.Pattern, error) { +func ReadPatterns(prefix string, inputTree *InputTreeNode, path []string, accumulator []gitignore.Pattern, lastRelation Relation) ([]gitignore.Pattern, error) { ps, _ := readIgnoreFile(prefix, path, ".gitignore") accumulator = append(accumulator, ps...) - absPath := append(prefix, path...) + absPath := append([]string{prefix}, path...) joinPath := filepath.Join(absPath...) + // fmt.Println(joinPath) var fis []os.DirEntry fis, err := os.ReadDir(joinPath) if err != nil { @@ -164,16 +167,22 @@ func ReadPatterns(prefix []string, inputTree *InputTreeNode, path []string, accu matcher := gitignore.NewMatcher(accumulator) for _, fi := range fis { name := fi.Name() + // fmt.Println(name) subPath := append(path, name) isDir := fi.IsDir() if matcher.Match(subPath, isDir) { continue } if isDir && name != ".git" { - if lastRelation != IsParent { - lastRelation = inputTree.Relation(append(absPath, name)) + // fmt.Println("In iteration", joinPath, name) + if lastRelation != IsChild { + splitPath := strings.Split(prefix, string(os.PathSeparator)) + splitPath = append(splitPath, path...) + splitPath = append(splitPath, name) + lastRelation = inputTree.Relation(splitPath) } if lastRelation == None { + // fmt.Println("lastRelation was None for", joinPath, name) continue } var subps []gitignore.Pattern From afafadd6817088a7e615f95bc6395bed9c5f7969 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 6 May 2021 08:46:17 -0400 Subject: [PATCH 42/65] [RM-5352] Cleaned up ReadPatterns a little --- pkg/git/input_tree.go | 45 ++++++++++++++++++++++++++++++++++++++----- pkg/git/repo.go | 43 +++++++++++++++-------------------------- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/pkg/git/input_tree.go b/pkg/git/input_tree.go index 721c824d..56d6deb1 100644 --- a/pkg/git/input_tree.go +++ b/pkg/git/input_tree.go @@ -10,8 +10,8 @@ type Relation int const ( None Relation = iota - IsParent - IsChild + TreeNodeIsChild + TreeNodeIsParent ) type InputTreeNode struct { @@ -29,14 +29,13 @@ func NewInputTreeNode(splitPath []string) *InputTreeNode { } func (t *InputTreeNode) Relation(splitPath []string) Relation { - // fmt.Println("Checking relation", strings.Join(splitPath, "/")) if pathLen := len(splitPath); pathLen < 1 { // In this case the splitPath is a parent of the tree node - return IsParent + return TreeNodeIsChild } else { if len(t.Children) == 0 { // In this case the tree node is a parent of the split path - return IsChild + return TreeNodeIsParent } if child, ok := t.Children[splitPath[0]]; ok { return child.Relation(splitPath[1:]) @@ -71,3 +70,39 @@ func NewInputTree(paths []string) *InputTreeNode { return rootNode } + +type SearchPath struct { + prefix string + path []string + splitPrefix []string +} + +func NewSearchPath(prefix string, path []string) SearchPath { + splitPrefix := strings.Split(prefix, string(os.PathSeparator)) + return SearchPath{ + prefix: prefix, + path: path, + splitPrefix: splitPrefix, + } +} + +func (s SearchPath) Abs() string { + fullPath := append([]string{s.prefix}, s.path...) + return filepath.Join(fullPath...) +} + +func (s SearchPath) WithAddedPath(path string) SearchPath { + return SearchPath{ + prefix: s.prefix, + path: append(s.path, path), + splitPrefix: s.splitPrefix, + } +} + +func (s SearchPath) AbsSplit() []string { + return append(s.splitPrefix, s.path...) +} + +func (s SearchPath) Path() []string { + return s.path +} diff --git a/pkg/git/repo.go b/pkg/git/repo.go index 36eca3c8..ba6cecf8 100644 --- a/pkg/git/repo.go +++ b/pkg/git/repo.go @@ -38,8 +38,7 @@ type repo struct { } func NewRepo(path string, inputTree *InputTreeNode) (Repo, error) { - // fsys := osfs.New(path) - patterns, err := ReadPatterns(path, inputTree, nil, nil, None) + patterns, err := ReadPatterns(NewSearchPath(path, nil), inputTree, nil, None) if err != nil { return nil, err } @@ -125,11 +124,9 @@ func (s *RepoFinder) FindRepo(path string) Repo { // Vendored from go-git so that we can fix their behavior // readIgnoreFile reads a specific git ignore file. -func readIgnoreFile(prefix string, path []string, ignoreFile string) (ps []gitignore.Pattern, err error) { - absPath := append(append([]string{prefix}, path...), ignoreFile) - joinPath := filepath.Join(absPath...) - // fmt.Println("Trying to read .gitignore file", joinPath) - f, err := os.Open(joinPath) +func readIgnoreFile(searchPath SearchPath) (ps []gitignore.Pattern, err error) { + ignoreFilePath := searchPath.WithAddedPath(".gitignore").Abs() + f, err := os.Open(ignoreFilePath) if err == nil { defer f.Close() @@ -137,7 +134,7 @@ func readIgnoreFile(prefix string, path []string, ignoreFile string) (ps []gitig for scanner.Scan() { s := scanner.Text() if !strings.HasPrefix(s, "#") && len(strings.TrimSpace(s)) > 0 { - ps = append(ps, gitignore.ParsePattern(s, path)) + ps = append(ps, gitignore.ParsePattern(s, searchPath.Path())) } } } else if !os.IsNotExist(err) { @@ -151,15 +148,12 @@ func readIgnoreFile(prefix string, path []string, ignoreFile string) (ps []gitig // structure. The result is in the ascending order of priority (last higher). This // function has been modified to respect gitignore patterns while it's traversing. This // has a big impact for larger repositories. -func ReadPatterns(prefix string, inputTree *InputTreeNode, path []string, accumulator []gitignore.Pattern, lastRelation Relation) ([]gitignore.Pattern, error) { - ps, _ := readIgnoreFile(prefix, path, ".gitignore") +func ReadPatterns(searchPath SearchPath, inputTree *InputTreeNode, accumulator []gitignore.Pattern, lastRelation Relation) ([]gitignore.Pattern, error) { + ps, _ := readIgnoreFile(searchPath) accumulator = append(accumulator, ps...) - absPath := append([]string{prefix}, path...) - joinPath := filepath.Join(absPath...) - // fmt.Println(joinPath) var fis []os.DirEntry - fis, err := os.ReadDir(joinPath) + fis, err := os.ReadDir(searchPath.Abs()) if err != nil { return nil, err } @@ -167,26 +161,21 @@ func ReadPatterns(prefix string, inputTree *InputTreeNode, path []string, accumu matcher := gitignore.NewMatcher(accumulator) for _, fi := range fis { name := fi.Name() - // fmt.Println(name) - subPath := append(path, name) isDir := fi.IsDir() - if matcher.Match(subPath, isDir) { - continue - } + if isDir && name != ".git" { - // fmt.Println("In iteration", joinPath, name) - if lastRelation != IsChild { - splitPath := strings.Split(prefix, string(os.PathSeparator)) - splitPath = append(splitPath, path...) - splitPath = append(splitPath, name) - lastRelation = inputTree.Relation(splitPath) + subPath := searchPath.WithAddedPath(name) + if matcher.Match(subPath.Path(), isDir) { + continue + } + if lastRelation != TreeNodeIsParent { + lastRelation = inputTree.Relation(subPath.AbsSplit()) } if lastRelation == None { - // fmt.Println("lastRelation was None for", joinPath, name) continue } var subps []gitignore.Pattern - subps, err = ReadPatterns(prefix, inputTree, subPath, accumulator, lastRelation) + subps, err = ReadPatterns(subPath, inputTree, accumulator, lastRelation) if err != nil { return nil, err } From d8c5c8f06cf8f8a4bff774b2f45095269fb367d0 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 6 May 2021 12:06:54 -0400 Subject: [PATCH 43/65] [RM-5352] Better banner for REPL --- Dockerfile | 2 +- Makefile | 4 ++-- cmd/root.go | 3 ++- pkg/rego/runrepl.go | 19 ++++++++++++++++++- {cmd => pkg/version}/version.go | 9 ++++++++- 5 files changed, 31 insertions(+), 6 deletions(-) rename {cmd => pkg/version}/version.go (80%) diff --git a/Dockerfile b/Dockerfile index 68a922c7..a4ed47ec 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ARG version ARG gitcommit WORKDIR /build COPY . . -ENV ldflags "-X \"github.com/fugue/regula/cmd.Version=${version}\" -X \"github.com/fugue/regula/cmd.GitCommit=${gitcommit}\"" +ENV ldflags "-X \"github.com/fugue/regula/pkg/version.Version=${version}\" -X \"github.com/fugue/regula/pkg/version.GitCommit=${gitcommit}\"" RUN go build -ldflags="${ldflags} -s -w" FROM alpine:latest diff --git a/Makefile b/Makefile index a38668ef..eabaf7c2 100644 --- a/Makefile +++ b/Makefile @@ -9,8 +9,8 @@ REGO_RULES_SOURCE = $(shell find rego/rules -type f -name '*.rego') VERSION = $(shell cat VERSION) GITCOMMIT = $(shell git rev-parse --short HEAD 2> /dev/null || true) define LDFLAGS - -X \"github.com/fugue/regula/cmd.Version=$(VERSION)\" \ - -X \"github.com/fugue/regula/cmd.GitCommit=$(GITCOMMIT)\" + -X \"github.com/fugue/regula/pkg/version.Version=$(VERSION)\" \ + -X \"github.com/fugue/regula/pkg/version.GitCommit=$(GITCOMMIT)\" endef CLI_BUILD = go build -ldflags="$(LDFLAGS) -s -w" GO_BIN_DIR= $(shell go env GOPATH)/bin diff --git a/cmd/root.go b/cmd/root.go index f44bb064..a3214927 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,13 +18,14 @@ import ( "fmt" "os" + "github.com/fugue/regula/pkg/version" "github.com/spf13/cobra" ) var rootCmd = &cobra.Command{ Use: "regula", Short: "Regula", - Version: fmt.Sprintf("%s, build %s", Version, GitCommit), + Version: fmt.Sprintf("%s, build %s", version.Version, version.GitCommit), } func Execute() { diff --git a/pkg/rego/runrepl.go b/pkg/rego/runrepl.go index dce45e5c..0e09367d 100644 --- a/pkg/rego/runrepl.go +++ b/pkg/rego/runrepl.go @@ -2,8 +2,12 @@ package rego import ( "context" + "fmt" "os" + "strings" + "github.com/fugue/regula/pkg/version" + "github.com/open-policy-agent/opa/ast" "github.com/open-policy-agent/opa/repl" "github.com/open-policy-agent/opa/storage" "github.com/open-policy-agent/opa/storage/inmem" @@ -21,7 +25,13 @@ func RunREPL(options *RunREPLOptions) error { if err != nil { return err } - r := repl.New(store, "./.regula-history", os.Stdout, "", 50, "Regula REPL") + r := repl.New( + store, + "./.regula-history", + os.Stdout, + "pretty", + ast.CompileErrorLimitDefault, + getBanner()) r.Loop(options.Ctx) return nil } @@ -48,3 +58,10 @@ func initStore(ctx context.Context, userOnly bool, includes []string) (storage.S } return store, nil } + +func getBanner() string { + var sb strings.Builder + sb.WriteString(fmt.Sprintf("Regula v%v - built on OPA v%v\n", version.Version, version.OPAVersion)) + sb.WriteString("Run 'help' to see a list of commands.") + return sb.String() +} diff --git a/cmd/version.go b/pkg/version/version.go similarity index 80% rename from cmd/version.go rename to pkg/version/version.go index 8bb4dd96..925b856d 100644 --- a/cmd/version.go +++ b/pkg/version/version.go @@ -12,7 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cmd +package version + +import ( + "github.com/open-policy-agent/opa/version" +) // Default build-time variables. // These values are overridden via ldflags @@ -20,3 +24,6 @@ var ( Version = "unknown-version" GitCommit = "unknown-commit" ) + +// OPAVersion is the canonical version of OPA that is embedded in Regula +var OPAVersion = version.Version From 9cee229f3c7916c78c9e53c6b3716267925cdc2b Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 6 May 2021 12:11:30 -0400 Subject: [PATCH 44/65] [RM-5352] Include OPA version in regula --version --- cmd/root.go | 2 +- pkg/rego/runrepl.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index a3214927..9ef7e628 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,7 +25,7 @@ import ( var rootCmd = &cobra.Command{ Use: "regula", Short: "Regula", - Version: fmt.Sprintf("%s, build %s", version.Version, version.GitCommit), + Version: fmt.Sprintf("v%s, build %s, built with OPA v%s", version.Version, version.GitCommit, version.OPAVersion), } func Execute() { diff --git a/pkg/rego/runrepl.go b/pkg/rego/runrepl.go index 0e09367d..ac1f4f3c 100644 --- a/pkg/rego/runrepl.go +++ b/pkg/rego/runrepl.go @@ -61,7 +61,7 @@ func initStore(ctx context.Context, userOnly bool, includes []string) (storage.S func getBanner() string { var sb strings.Builder - sb.WriteString(fmt.Sprintf("Regula v%v - built on OPA v%v\n", version.Version, version.OPAVersion)) + sb.WriteString(fmt.Sprintf("Regula v%v - built with OPA v%v\n", version.Version, version.OPAVersion)) sb.WriteString("Run 'help' to see a list of commands.") return sb.String() } From c09335716e34ccbf2d7f997d0ee65840d9012796 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 6 May 2021 14:57:18 -0400 Subject: [PATCH 45/65] [RM-5352] Fix REPL help text and use homedir for .regula-history --- cmd/repl.go | 2 +- pkg/rego/runrepl.go | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/repl.go b/cmd/repl.go index a690ca54..f7a2f5d2 100644 --- a/cmd/repl.go +++ b/cmd/repl.go @@ -28,7 +28,7 @@ import ( func NewREPLCommand() *cobra.Command { cmd := &cobra.Command{ Use: "repl [rego paths]", - Short: "Evaluate rules against infrastructure-as-code with Regula.", + Short: "Start an interactive session for testing rules with Regula", Run: func(cmd *cobra.Command, includes []string) { userOnly, err := cmd.Flags().GetBool("user-only") if err != nil { diff --git a/pkg/rego/runrepl.go b/pkg/rego/runrepl.go index ac1f4f3c..8c1ca736 100644 --- a/pkg/rego/runrepl.go +++ b/pkg/rego/runrepl.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "path/filepath" "strings" "github.com/fugue/regula/pkg/version" @@ -25,9 +26,15 @@ func RunREPL(options *RunREPLOptions) error { if err != nil { return err } + var historyPath string + if homeDir, err := os.UserHomeDir(); err == nil { + historyPath = filepath.Join(homeDir, ".regula-history") + } else { + historyPath = filepath.Join(".", ".regula-history") + } r := repl.New( store, - "./.regula-history", + historyPath, os.Stdout, "pretty", ast.CompileErrorLimitDefault, From 1ad7f66c98d23ed77481db4329577b1f3208e860 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Fri, 7 May 2021 13:58:53 -0400 Subject: [PATCH 46/65] [RM-5352] Handle error from rego.RunRules() --- cmd/run.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/run.go b/cmd/run.go index 2ceed054..fc3637ca 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -85,6 +85,10 @@ func NewRunCommand() *cobra.Command { Includes: includes, Input: loadedFiles.RegulaInput(), }) + if err != nil { + fmt.Println(err) + os.Exit(1) + } reporterFunc, err := reporter.GetReporter(format) if err != nil { fmt.Println(err) From 5cd43aea383b6152e1981d9e931f77b802399e0b Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 11 May 2021 09:02:11 -0400 Subject: [PATCH 47/65] [RM-5352] Fix stdin detection with docker w/o -it flags --- cmd/run.go | 8 ++++---- go.mod | 1 - go.sum | 2 -- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/cmd/run.go b/cmd/run.go index fc3637ca..e8709b51 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -25,7 +25,6 @@ import ( "github.com/fugue/regula/pkg/reporter" "github.com/spf13/cobra" "github.com/thediveo/enumflag" - "golang.org/x/crypto/ssh/terminal" ) //go:embed run.txt @@ -61,12 +60,13 @@ func NewRunCommand() *cobra.Command { os.Exit(1) } if len(paths) < 1 { - if terminal.IsTerminal(int(os.Stdin.Fd())) { + stat, _ := os.Stdin.Stat() + if (stat.Mode() & os.ModeCharDevice) == 0 { + paths = []string{"-"} + } else { // Not using os.Getwd here so that we get relative paths. // A single dot should mean the same on windows. paths = []string{"."} - } else { - paths = []string{"-"} } } diff --git a/go.mod b/go.mod index aeb127de..869444ea 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,5 @@ require ( github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.4.0 github.com/thediveo/enumflag v0.10.1 - golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) diff --git a/go.sum b/go.sum index 36cc6831..e4186fba 100644 --- a/go.sum +++ b/go.sum @@ -418,7 +418,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -507,7 +506,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From d2cec3ee0ee47bd89b03db3b6d4e5409498e9709 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 11 May 2021 13:19:58 -0400 Subject: [PATCH 48/65] [RM-5352] Added some tests to pkg/loader --- pkg/loader/cfn_test.go | 63 ++- pkg/loader/loadpaths_test.go | 47 ++ pkg/loader/test_inputs/{cfn => data}/cfn.json | 0 pkg/loader/test_inputs/{cfn => data}/cfn.yaml | 0 .../test_inputs/data/cfn_intrinsics.json | 215 ++++++++ .../test_inputs/data/cfn_intrinsics.yaml | 162 ++++++ .../{cfn => data}/cfn_resources.yaml | 0 .../test_inputs/{cfn => data}/other.json | 0 .../{cfn/not_yaml.txt => data/text.txt} | 0 pkg/loader/test_inputs/data/tfplan.0.12.json | 412 +++++++++++++++ pkg/loader/test_inputs/data/tfplan.0.13.json | 416 +++++++++++++++ pkg/loader/test_inputs/data/tfplan.0.14.json | 416 +++++++++++++++ pkg/loader/test_inputs/data/tfplan.0.15.json | 476 ++++++++++++++++++ pkg/loader/test_inputs/test_inputs.go | 20 + pkg/loader/tfplan_test.go | 85 ++++ 15 files changed, 2289 insertions(+), 23 deletions(-) create mode 100644 pkg/loader/loadpaths_test.go rename pkg/loader/test_inputs/{cfn => data}/cfn.json (100%) rename pkg/loader/test_inputs/{cfn => data}/cfn.yaml (100%) create mode 100644 pkg/loader/test_inputs/data/cfn_intrinsics.json create mode 100644 pkg/loader/test_inputs/data/cfn_intrinsics.yaml rename pkg/loader/test_inputs/{cfn => data}/cfn_resources.yaml (100%) rename pkg/loader/test_inputs/{cfn => data}/other.json (100%) rename pkg/loader/test_inputs/{cfn/not_yaml.txt => data/text.txt} (100%) create mode 100644 pkg/loader/test_inputs/data/tfplan.0.12.json create mode 100644 pkg/loader/test_inputs/data/tfplan.0.13.json create mode 100644 pkg/loader/test_inputs/data/tfplan.0.14.json create mode 100644 pkg/loader/test_inputs/data/tfplan.0.15.json create mode 100644 pkg/loader/test_inputs/test_inputs.go create mode 100644 pkg/loader/tfplan_test.go diff --git a/pkg/loader/cfn_test.go b/pkg/loader/cfn_test.go index 48d7a4a4..3f2e1116 100644 --- a/pkg/loader/cfn_test.go +++ b/pkg/loader/cfn_test.go @@ -1,30 +1,16 @@ package loader_test import ( - _ "embed" + "encoding/json" "testing" "github.com/fugue/regula/pkg/loader" + inputs "github.com/fugue/regula/pkg/loader/test_inputs" "github.com/fugue/regula/pkg/mocks" "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" ) -//go:embed test_inputs/cfn/cfn.yaml -var cfnYAMLContents []byte - -//go:embed test_inputs/cfn/cfn.json -var cfnJSONContents []byte - -//go:embed test_inputs/cfn/cfn_resources.yaml -var cfnYAMLResourcesContents []byte - -//go:embed test_inputs/cfn/other.json -var otherJSONContents []byte - -//go:embed test_inputs/cfn/not_yaml.txt -var notYAMLContents []byte - func makeMockFile(ctrl *gomock.Controller, path, ext string, contents []byte) loader.InputFile { mockFile := mocks.NewMockInputFile(ctrl) mockFile.EXPECT().Ext().Return(ext) @@ -40,10 +26,10 @@ func TestCfnDetector(t *testing.T) { ext string contents []byte }{ - {path: "cfn.yaml", ext: ".yaml", contents: cfnYAMLContents}, - {path: "cfn.yml", ext: ".yml", contents: cfnYAMLContents}, - {path: "cfn.json", ext: ".yaml", contents: cfnJSONContents}, - {path: "cfn_resources.yaml", ext: ".yaml", contents: cfnYAMLResourcesContents}, + {path: "cfn.yaml", ext: ".yaml", contents: inputs.Contents(t, "cfn.yaml")}, + {path: "cfn.yml", ext: ".yml", contents: inputs.Contents(t, "cfn.yaml")}, + {path: "cfn.json", ext: ".yaml", contents: inputs.Contents(t, "cfn.json")}, + {path: "cfn_resources.yaml", ext: ".yaml", contents: inputs.Contents(t, "cfn_resources.yaml")}, } detector := &loader.CfnDetector{} @@ -61,7 +47,7 @@ func TestCfnDetector(t *testing.T) { func TestCfnDetectorNotCfnContents(t *testing.T) { ctrl := gomock.NewController(t) detector := &loader.CfnDetector{} - f := makeMockFile(ctrl, "other.json", ".json", otherJSONContents) + f := makeMockFile(ctrl, "other.json", ".json", inputs.Contents(t, "other.json")) loader, err := detector.DetectFile(f, loader.DetectOptions{ IgnoreExt: false, }) @@ -87,7 +73,7 @@ func TestCfnDetectorIgnoreExt(t *testing.T) { detector := &loader.CfnDetector{} f := mocks.NewMockInputFile(ctrl) f.EXPECT().Path().Return("cfn.cfn") - f.EXPECT().Contents().Return(cfnYAMLContents, nil) + f.EXPECT().Contents().Return(inputs.Contents(t, "cfn.yaml"), nil) loader, err := detector.DetectFile(f, loader.DetectOptions{ IgnoreExt: true, }) @@ -99,10 +85,41 @@ func TestCfnDetectorIgnoreExt(t *testing.T) { func TestCfnDetectorNotYAML(t *testing.T) { ctrl := gomock.NewController(t) detector := &loader.CfnDetector{} - f := makeMockFile(ctrl, "not_cfn.yaml", ".yaml", notYAMLContents) + f := makeMockFile(ctrl, "not_cfn.yaml", ".yaml", inputs.Contents(t, "text.txt")) loader, err := detector.DetectFile(f, loader.DetectOptions{ IgnoreExt: false, }) assert.NotNil(t, err) assert.Nil(t, loader) } + +func TestCfnIntrinsics(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.CfnDetector{} + yamlFile := makeMockFile(ctrl, "cfn.yaml", ".yaml", inputs.Contents(t, "cfn_intrinsics.yaml")) + // This JSON file was produced with cfn-flip. The transformations performed by the + // loader should be identical to the output of cfn-flip. + jsonFile := makeMockFile(ctrl, "cfn.json", ".json", inputs.Contents(t, "cfn_intrinsics.json")) + yamlLoader, err := detector.DetectFile(yamlFile, loader.DetectOptions{}) + assert.Nil(t, err) + assert.NotNil(t, yamlLoader) + + jsonLoader, err := detector.DetectFile(jsonFile, loader.DetectOptions{}) + assert.Nil(t, err) + assert.NotNil(t, jsonLoader) + + yamlInput := coerceRegulaInput(t, yamlLoader.RegulaInput()) + jsonInput := coerceRegulaInput(t, jsonLoader.RegulaInput()) + + assert.Equal(t, jsonInput["content"], yamlInput["content"]) +} + +// This is annoying, but we care about the values (not the types) +func coerceRegulaInput(t *testing.T, regulaInput loader.RegulaInput) loader.RegulaInput { + coerced := loader.RegulaInput{} + bytes, err := json.Marshal(regulaInput) + assert.Nil(t, err) + err = json.Unmarshal(bytes, &coerced) + + return coerced +} diff --git a/pkg/loader/loadpaths_test.go b/pkg/loader/loadpaths_test.go new file mode 100644 index 00000000..b6b7b8e5 --- /dev/null +++ b/pkg/loader/loadpaths_test.go @@ -0,0 +1,47 @@ +package loader_test + +import ( + "testing" + + "github.com/fugue/regula/pkg/loader" + "github.com/stretchr/testify/assert" +) + +func TestLoadPathsDirAuto(t *testing.T) { + loadedConfigs, err := loader.LoadPaths(loader.LoadPathsOptions{ + Paths: []string{"test_inputs/data"}, + InputType: loader.Auto, + }) + assert.Nil(t, err) + assert.NotNil(t, loadedConfigs) + assert.Greater(t, loadedConfigs.Count(), 0) + assert.True(t, loadedConfigs.AlreadyLoaded("test_inputs/data/tfplan.0.15.json")) + assert.True(t, loadedConfigs.AlreadyLoaded("test_inputs/data/cfn.yaml")) +} + +func TestLoadPathsFiles(t *testing.T) { + loadedConfigs, err := loader.LoadPaths(loader.LoadPathsOptions{ + Paths: []string{ + "test_inputs/data/cfn.yaml", + "test_inputs/data/tfplan.0.15.json", + }, + InputType: loader.Auto, + }) + assert.Nil(t, err) + assert.NotNil(t, loadedConfigs) + assert.Equal(t, 2, loadedConfigs.Count()) + assert.True(t, loadedConfigs.AlreadyLoaded("test_inputs/data/tfplan.0.15.json")) + assert.True(t, loadedConfigs.AlreadyLoaded("test_inputs/data/cfn.yaml")) +} + +func TestLoadPathsDirWithType(t *testing.T) { + loadedConfigs, err := loader.LoadPaths(loader.LoadPathsOptions{ + Paths: []string{"test_inputs/data"}, + InputType: loader.TfPlan, + }) + assert.Nil(t, err) + assert.NotNil(t, loadedConfigs) + assert.Greater(t, loadedConfigs.Count(), 0) + assert.True(t, loadedConfigs.AlreadyLoaded("test_inputs/data/tfplan.0.15.json")) + assert.False(t, loadedConfigs.AlreadyLoaded("test_inputs/data/cfn.yaml")) +} diff --git a/pkg/loader/test_inputs/cfn/cfn.json b/pkg/loader/test_inputs/data/cfn.json similarity index 100% rename from pkg/loader/test_inputs/cfn/cfn.json rename to pkg/loader/test_inputs/data/cfn.json diff --git a/pkg/loader/test_inputs/cfn/cfn.yaml b/pkg/loader/test_inputs/data/cfn.yaml similarity index 100% rename from pkg/loader/test_inputs/cfn/cfn.yaml rename to pkg/loader/test_inputs/data/cfn.yaml diff --git a/pkg/loader/test_inputs/data/cfn_intrinsics.json b/pkg/loader/test_inputs/data/cfn_intrinsics.json new file mode 100644 index 00000000..5ff2c0dc --- /dev/null +++ b/pkg/loader/test_inputs/data/cfn_intrinsics.json @@ -0,0 +1,215 @@ +{ + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Invalid public function configuration", + "Resources": { + "FunctionRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Path": "/" + } + }, + "Function": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + }, + "FunctionPermissionByArn": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::GetAtt": [ + "Function", + "Arn" + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "*" + } + }, + "Function2": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + }, + "FunctionPermissionByRef": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Ref": "Function2" + }, + "Action": "lambda:InvokeFunction", + "Principal": "*" + } + }, + "Function3": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + }, + "FunctionPermissionByPartialArn": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::Sub": "${AWS::AccountId}:${Function3}" + }, + "Action": "lambda:InvokeFunction", + "Principal": "*" + } + }, + "Function4": { + "Type": "AWS::Lambda::Function", + "Properties": { + "FunctionName": "function4", + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + }, + "FunctionPermissionByHardcodedName": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": "function4", + "Action": "lambda:InvokeFunction", + "Principal": "*" + } + }, + "Function5Alias": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "FunctionName": { + "Ref": "Function5" + }, + "FunctionVersion": "$LATEST", + "Name": "v1" + } + }, + "Function5": { + "Type": "AWS::Lambda::Function", + "Properties": { + "FunctionName": "function5", + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + }, + "FunctionPermissionByHardcodedNameAndAlias": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": "function5:v1", + "Action": "lambda:InvokeFunction", + "Principal": "*" + } + }, + "Function6Alias": { + "Type": "AWS::Lambda::Alias", + "Properties": { + "FunctionName": { + "Ref": "Function5" + }, + "FunctionVersion": "$LATEST", + "Name": "v1" + } + }, + "Function6": { + "Type": "AWS::Lambda::Function", + "Properties": { + "FunctionName": { + "Fn::Sub": "function-${AWS::Region}" + }, + "Code": { + "ZipFile": "exports.handler = (event, context) => {\n console.log(JSON.stringify(event))\n}\n" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "FunctionRole", + "Arn" + ] + }, + "Runtime": "nodejs12.x" + } + }, + "FunctionPermissionByNameAndAliasUsingFunctions": { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { + "Fn::Join": [ + ":", + [ + { + "Fn::Sub": "function-${AWS::Region}" + }, + "v2" + ] + ] + }, + "Action": "lambda:InvokeFunction", + "Principal": "*" + } + } + } +} \ No newline at end of file diff --git a/pkg/loader/test_inputs/data/cfn_intrinsics.yaml b/pkg/loader/test_inputs/data/cfn_intrinsics.yaml new file mode 100644 index 00000000..fdb24620 --- /dev/null +++ b/pkg/loader/test_inputs/data/cfn_intrinsics.yaml @@ -0,0 +1,162 @@ +# Copyright 2020-2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +AWSTemplateFormatVersion: "2010-09-09" +Description: Invalid public function configuration +Resources: + FunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + Path: / + + # These don't represent every possible way of specifying FunctionName + # on permissions, but it should be sufficient to test that our method of + # associating functions and permissions is working + + # Associating permissions by ARN + Function: + Type: AWS::Lambda::Function + Properties: + Code: + ZipFile: | + exports.handler = (event, context) => { + console.log(JSON.stringify(event)) + } + Handler: index.handler + Role: !GetAtt FunctionRole.Arn + Runtime: nodejs12.x + FunctionPermissionByArn: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !GetAtt Function.Arn + Action: lambda:InvokeFunction + Principal: "*" + + # Associating permissions by Ref + Function2: + Type: AWS::Lambda::Function + Properties: + Code: + ZipFile: | + exports.handler = (event, context) => { + console.log(JSON.stringify(event)) + } + Handler: index.handler + Role: !GetAtt FunctionRole.Arn + Runtime: nodejs12.x + FunctionPermissionByRef: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref Function2 + Action: lambda:InvokeFunction + Principal: "*" + + # Associating permissions by Partial ARN with sub + Function3: + Type: AWS::Lambda::Function + Properties: + Code: + ZipFile: | + exports.handler = (event, context) => { + console.log(JSON.stringify(event)) + } + Handler: index.handler + Role: !GetAtt FunctionRole.Arn + Runtime: nodejs12.x + FunctionPermissionByPartialArn: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Sub "${AWS::AccountId}:${Function3}" + Action: lambda:InvokeFunction + Principal: "*" + + # Associating permissions by hardcoded name + Function4: + Type: AWS::Lambda::Function + Properties: + FunctionName: function4 + Code: + ZipFile: | + exports.handler = (event, context) => { + console.log(JSON.stringify(event)) + } + Handler: index.handler + Role: !GetAtt FunctionRole.Arn + Runtime: nodejs12.x + FunctionPermissionByHardcodedName: + Type: AWS::Lambda::Permission + Properties: + FunctionName: function4 + Action: lambda:InvokeFunction + Principal: "*" + + # Associating permissions by hardcoded name and alias + Function5Alias: + Type: AWS::Lambda::Alias + Properties: + FunctionName: !Ref Function5 + FunctionVersion: $LATEST + Name: v1 + Function5: + Type: AWS::Lambda::Function + Properties: + FunctionName: function5 + Code: + ZipFile: | + exports.handler = (event, context) => { + console.log(JSON.stringify(event)) + } + Handler: index.handler + Role: !GetAtt FunctionRole.Arn + Runtime: nodejs12.x + FunctionPermissionByHardcodedNameAndAlias: + Type: AWS::Lambda::Permission + Properties: + FunctionName: function5:v1 + Action: lambda:InvokeFunction + Principal: "*" + + # Associating permissions by hardcoded name and alias, using functions + Function6Alias: + Type: AWS::Lambda::Alias + Properties: + FunctionName: !Ref Function5 + FunctionVersion: $LATEST + Name: v1 + Function6: + Type: AWS::Lambda::Function + Properties: + FunctionName: !Sub "function-${AWS::Region}" + Code: + ZipFile: | + exports.handler = (event, context) => { + console.log(JSON.stringify(event)) + } + Handler: index.handler + Role: !GetAtt FunctionRole.Arn + Runtime: nodejs12.x + FunctionPermissionByNameAndAliasUsingFunctions: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Join [":", [!Sub "function-${AWS::Region}", "v2"]] + Action: lambda:InvokeFunction + Principal: "*" diff --git a/pkg/loader/test_inputs/cfn/cfn_resources.yaml b/pkg/loader/test_inputs/data/cfn_resources.yaml similarity index 100% rename from pkg/loader/test_inputs/cfn/cfn_resources.yaml rename to pkg/loader/test_inputs/data/cfn_resources.yaml diff --git a/pkg/loader/test_inputs/cfn/other.json b/pkg/loader/test_inputs/data/other.json similarity index 100% rename from pkg/loader/test_inputs/cfn/other.json rename to pkg/loader/test_inputs/data/other.json diff --git a/pkg/loader/test_inputs/cfn/not_yaml.txt b/pkg/loader/test_inputs/data/text.txt similarity index 100% rename from pkg/loader/test_inputs/cfn/not_yaml.txt rename to pkg/loader/test_inputs/data/text.txt diff --git a/pkg/loader/test_inputs/data/tfplan.0.12.json b/pkg/loader/test_inputs/data/tfplan.0.12.json new file mode 100644 index 00000000..fc54cedc --- /dev/null +++ b/pkg/loader/test_inputs/data/tfplan.0.12.json @@ -0,0 +1,412 @@ +{ + "format_version": "0.1", + "terraform_version": "0.12.20", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "vpc_id": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_2" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_2" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/pkg/loader/test_inputs/data/tfplan.0.13.json b/pkg/loader/test_inputs/data/tfplan.0.13.json new file mode 100644 index 00000000..3a7759bb --- /dev/null +++ b/pkg/loader/test_inputs/data/tfplan.0.13.json @@ -0,0 +1,416 @@ +{ + "format_version": "0.1", + "terraform_version": "0.13.5", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_2" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_2" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/pkg/loader/test_inputs/data/tfplan.0.14.json b/pkg/loader/test_inputs/data/tfplan.0.14.json new file mode 100644 index 00000000..169e2dfb --- /dev/null +++ b/pkg/loader/test_inputs/data/tfplan.0.14.json @@ -0,0 +1,416 @@ +{ + "format_version": "0.1", + "terraform_version": "0.14.11", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_2" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_2" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/pkg/loader/test_inputs/data/tfplan.0.15.json b/pkg/loader/test_inputs/data/tfplan.0.15.json new file mode 100644 index 00000000..584c0190 --- /dev/null +++ b/pkg/loader/test_inputs/data/tfplan.0.15.json @@ -0,0 +1,476 @@ +{ + "format_version": "0.1", + "terraform_version": "0.15.3", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "schema_version": 1, + "values": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + } + } + ] + } + }, + "resource_changes": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "invalid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "egress": [], + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "tags_all": {} + } + } + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 20, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 25 + } + ], + "name": "invalid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "egress": [], + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "tags_all": {} + } + } + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "valid_sg_1", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "egress": [], + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "tags_all": {} + } + } + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_name": "registry.terraform.io/hashicorp/aws", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": "Managed by Terraform", + "ingress": [ + { + "cidr_blocks": [ + "10.10.0.0/16" + ], + "description": "", + "from_port": 22, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 22 + } + ], + "name": "valid_sg_2", + "revoke_rules_on_delete": false, + "tags": null, + "timeouts": null + }, + "after_unknown": { + "arn": true, + "egress": true, + "id": true, + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "name_prefix": true, + "owner_id": true, + "tags_all": true, + "vpc_id": true + }, + "before_sensitive": false, + "after_sensitive": { + "egress": [], + "ingress": [ + { + "cidr_blocks": [ + false + ], + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "security_groups": [] + } + ], + "tags_all": {} + } + } + } + ], + "configuration": { + "provider_config": { + "aws": { + "name": "aws", + "expressions": { + "region": { + "constant_value": "us-east-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "aws_security_group.invalid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.invalid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "invalid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "invalid_sg_2" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_1", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_1", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_1" + } + }, + "schema_version": 1 + }, + { + "address": "aws_security_group.valid_sg_2", + "mode": "managed", + "type": "aws_security_group", + "name": "valid_sg_2", + "provider_config_key": "aws", + "expressions": { + "name": { + "constant_value": "valid_sg_2" + } + }, + "schema_version": 1 + } + ] + } + } +} diff --git a/pkg/loader/test_inputs/test_inputs.go b/pkg/loader/test_inputs/test_inputs.go new file mode 100644 index 00000000..63a9acba --- /dev/null +++ b/pkg/loader/test_inputs/test_inputs.go @@ -0,0 +1,20 @@ +package test_inputs + +import ( + "embed" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" +) + +//go:embed data +var data embed.FS + +func Contents(t *testing.T, name string) []byte { + contents, err := data.ReadFile(filepath.Join("data", name)) + if err != nil { + assert.FailNow(t, err.Error()) + } + return contents +} diff --git a/pkg/loader/tfplan_test.go b/pkg/loader/tfplan_test.go new file mode 100644 index 00000000..733a4a48 --- /dev/null +++ b/pkg/loader/tfplan_test.go @@ -0,0 +1,85 @@ +package loader_test + +import ( + "testing" + + "github.com/fugue/regula/pkg/loader" + inputs "github.com/fugue/regula/pkg/loader/test_inputs" + "github.com/fugue/regula/pkg/mocks" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" +) + +func TestTfPlanDetector(t *testing.T) { + ctrl := gomock.NewController(t) + testInputs := []struct { + path string + ext string + contents []byte + }{ + {path: "tfplan.json", ext: ".json", contents: inputs.Contents(t, "tfplan.0.12.json")}, + {path: "tfplan.json", ext: ".json", contents: inputs.Contents(t, "tfplan.0.13.json")}, + {path: "tfplan.json", ext: ".json", contents: inputs.Contents(t, "tfplan.0.14.json")}, + {path: "tfplan.json", ext: ".json", contents: inputs.Contents(t, "tfplan.0.15.json")}, + } + detector := &loader.TfPlanDetector{} + + for _, i := range testInputs { + f := makeMockFile(ctrl, i.path, i.ext, i.contents) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.Nil(t, err) + assert.NotNil(t, loader) + assert.Equal(t, loader.LoadedFiles(), []string{i.path}) + } +} + +func TestTfPlanDetectorNotTfContents(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.TfPlanDetector{} + f := makeMockFile(ctrl, "other.json", ".json", inputs.Contents(t, "other.json")) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.NotNil(t, err) + assert.Nil(t, loader) +} + +func TestTfPlanDetectorNotJsonExt(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.TfPlanDetector{} + f := mocks.NewMockInputFile(ctrl) + f.EXPECT().Ext().Return(".tfplan") + f.EXPECT().Path().Return("plan.tfplan") + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.NotNil(t, err) + assert.Nil(t, loader) +} + +func TestTfPlanDetectorIgnoreExt(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.TfPlanDetector{} + f := mocks.NewMockInputFile(ctrl) + f.EXPECT().Path().Return("plan.tfplan") + f.EXPECT().Contents().Return(inputs.Contents(t, "tfplan.0.15.json"), nil) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: true, + }) + assert.Nil(t, err) + assert.NotNil(t, loader) + assert.Equal(t, loader.LoadedFiles(), []string{"plan.tfplan"}) +} + +func TestTfPlanDetectorNotYAML(t *testing.T) { + ctrl := gomock.NewController(t) + detector := &loader.TfPlanDetector{} + f := makeMockFile(ctrl, "not_tfplan.json", ".json", inputs.Contents(t, "text.txt")) + loader, err := detector.DetectFile(f, loader.DetectOptions{ + IgnoreExt: false, + }) + assert.NotNil(t, err) + assert.Nil(t, loader) +} From c70b49b8c9f36d33f780780871c30a2d2d41ea5a Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Wed, 12 May 2021 13:30:44 -0400 Subject: [PATCH 49/65] [RM-5352] Roll back OPA dep to v0.25.2 for old dedupe behavior --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 869444ea..b81930ae 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-git/go-git/v5 v5.3.0 github.com/golang/mock v1.5.0 - github.com/open-policy-agent/opa v0.26.0 + github.com/open-policy-agent/opa v0.25.2 github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.4.0 github.com/thediveo/enumflag v0.10.1 diff --git a/go.sum b/go.sum index e4186fba..e6824b01 100644 --- a/go.sum +++ b/go.sum @@ -276,8 +276,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/open-policy-agent/opa v0.26.0 h1:FI0woFdGA73reU8OzSMzgHLFK+XeDMxKIlBpvvpRqDQ= -github.com/open-policy-agent/opa v0.26.0/go.mod h1:iGThTRECCfKQKICueOZkXUi0opN7BR3qiAnIrNHCmlI= +github.com/open-policy-agent/opa v0.25.2 h1:zTQuUMvB5xkYixKB9LFVbUd7DcUt1jfS0QKTo+/Vfyc= +github.com/open-policy-agent/opa v0.25.2/go.mod h1:iGThTRECCfKQKICueOZkXUi0opN7BR3qiAnIrNHCmlI= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= From fe1651b42e37637d56a2b6a2ebf069d4e92ad0d0 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 13 May 2021 10:27:06 -0400 Subject: [PATCH 50/65] [RM-5352] Bump OPA back to 0.28.0 --- go.mod | 2 +- go.sum | 28 ++++++++++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index b81930ae..11602277 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-git/go-git/v5 v5.3.0 github.com/golang/mock v1.5.0 - github.com/open-policy-agent/opa v0.25.2 + github.com/open-policy-agent/opa v0.28.0 github.com/spf13/cobra v1.1.3 github.com/stretchr/testify v1.4.0 github.com/thediveo/enumflag v0.10.1 diff --git a/go.sum b/go.sum index e6824b01..94abe689 100644 --- a/go.sum +++ b/go.sum @@ -48,6 +48,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bytecodealliance/wasmtime-go v0.26.0 h1:wHOt9u+irLBCUjotanqDwVbnNmTJ1gWQxY2+q+XeMp4= +github.com/bytecodealliance/wasmtime-go v0.26.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -88,6 +90,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -276,8 +280,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/open-policy-agent/opa v0.25.2 h1:zTQuUMvB5xkYixKB9LFVbUd7DcUt1jfS0QKTo+/Vfyc= -github.com/open-policy-agent/opa v0.25.2/go.mod h1:iGThTRECCfKQKICueOZkXUi0opN7BR3qiAnIrNHCmlI= +github.com/open-policy-agent/opa v0.28.0 h1:y4e4oNVqCKXyz2nIhVNLVwZUa4+T/N8Spch73E8Deo0= +github.com/open-policy-agent/opa v0.28.0/go.mod h1:jYuhmtyoJI9HSLgVWEqUwfKecsLi/8wk0Uv76misZDU= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -384,10 +388,11 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/wasmerio/go-ext-wasm v0.3.1 h1:G95XP3fE2FszQSwIU+fHPBYzD0Csmd2ef33snQXNA5Q= -github.com/wasmerio/go-ext-wasm v0.3.1/go.mod h1:VGyarTzasuS7k5KhSIGpM3tciSZlkP31Mp9VJTHMMeI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= @@ -404,6 +409,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= @@ -433,7 +439,7 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -460,8 +466,7 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= @@ -474,7 +479,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -502,7 +507,9 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -516,6 +523,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -540,7 +548,7 @@ golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20201009032223-96877f285f7e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From b5ce21900a5c93332eecaaf65d613c3ef713af9c Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 13 May 2021 10:27:25 -0400 Subject: [PATCH 51/65] [RM-5352] Better errors for regula_load in repl --- pkg/rego/runrepl.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/rego/runrepl.go b/pkg/rego/runrepl.go index 8c1ca736..d0122ac4 100644 --- a/pkg/rego/runrepl.go +++ b/pkg/rego/runrepl.go @@ -39,6 +39,7 @@ func RunREPL(options *RunREPLOptions) error { "pretty", ast.CompileErrorLimitDefault, getBanner()) + r.OneShot(options.Ctx, "strict-builtin-errors") r.Loop(options.Ctx) return nil } From 37c61e4d2ba73e40bc58a6a638db5a9174dd3a19 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 13 May 2021 10:31:15 -0400 Subject: [PATCH 52/65] [RM-5352] Exclude regula dir in .gitignore --- .gitignore | 1 + rego/lib/fugue/regula/config.rego | 37 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 rego/lib/fugue/regula/config.rego diff --git a/.gitignore b/.gitignore index e149a95c..2e9e0b84 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ pkg/rego/lib pkg/rego/rules regula regula-* +!regula/ .vscode/ .scratch/ .regula-history diff --git a/rego/lib/fugue/regula/config.rego b/rego/lib/fugue/regula/config.rego new file mode 100644 index 00000000..7e3acef2 --- /dev/null +++ b/rego/lib/fugue/regula/config.rego @@ -0,0 +1,37 @@ +# Copyright 2021 Fugue, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This package provides a stub for users to add their own configuration. This +# way we ensure that the declared sets are incremental. +package fugue.regula.config + +waivers[waiver] { + false + waiver := { + "rule_id": "*", + "rule_name": "*", + "resource_id": "*", + "resource_type": "*", + "filepath": "*", + } +} + +rules[rule] { + false + rule := { + "rule_id": "some_rule_id", + "rule_name": "some_rule_name", + "status": "disabled", + } +} From 4b748e6ea55060e4989a9d928e5bf1e55a235cff Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Thu, 13 May 2021 11:09:33 -0400 Subject: [PATCH 53/65] [RM-5352] Move test_helper lib to fugue.regula.tests --- rego/lib/{test_helper.rego => fugue/regula/tests.rego} | 2 +- rego/scripts/generate-test-inputs.sh | 8 ++++---- rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego | 4 ++-- .../examples/aws/inputs/iam_password_length_infra.rego | 4 ++-- .../examples/aws/inputs/tag_all_resources_infra.rego | 4 ++-- rego/tests/examples/aws/inputs/useast1_only_infra.rego | 4 ++-- rego/tests/lib/inputs/invalid_encryption_infra.rego | 4 ++-- rego/tests/lib/inputs/resource_view_01_infra.rego | 4 ++-- rego/tests/lib/inputs/resource_view_02_infra.rego | 4 ++-- rego/tests/lib/inputs/resource_view_03_infra.rego | 4 ++-- rego/tests/lib/inputs/resource_view_04_infra.rego | 4 ++-- rego/tests/lib/inputs/valid_encryption_infra.rego | 4 ++-- rego/tests/lib/inputs/volume_encrypted_infra.rego | 4 ++-- .../inputs/invalid_classic_custom_domain_name_infra.rego | 4 ++-- .../invalid_classic_custom_domain_name_sam_infra.rego | 4 ++-- .../inputs/invalid_v2_custom_domain_name_infra.rego | 4 ++-- .../inputs/invalid_v2_custom_domain_name_sam_infra.rego | 4 ++-- .../inputs/valid_classic_custom_domain_name_infra.rego | 4 ++-- .../valid_classic_custom_domain_name_sam_infra.rego | 4 ++-- .../inputs/valid_v2_custom_domain_name_infra.rego | 4 ++-- .../inputs/valid_v2_custom_domain_name_sam_infra.rego | 4 ++-- .../rules/cfn/cloudtrail/inputs/empty_template_infra.rego | 4 ++-- .../cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego | 4 ++-- .../inputs/invalid_cloudwatch_with_valid_infra.rego | 4 ++-- .../cfn/cloudtrail/inputs/invalid_encryption_infra.rego | 4 ++-- .../cloudtrail/inputs/invalid_log_validation_infra.rego | 4 ++-- .../inputs/invalid_log_validation_with_valid_infra.rego | 4 ++-- .../inputs/invalid_s3_access_logging_infra.rego | 4 ++-- .../cloudtrail/inputs/invalid_target_public_infra.rego | 4 ++-- .../inputs/invalid_target_public_write_infra.rego | 4 ++-- .../cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego | 4 ++-- .../cfn/cloudtrail/inputs/valid_encryption_infra.rego | 4 ++-- .../cfn/cloudtrail/inputs/valid_log_validation_infra.rego | 4 ++-- .../cloudtrail/inputs/valid_s3_access_logging_infra.rego | 4 ++-- .../cloudtrail/inputs/valid_target_full_check_infra.rego | 4 ++-- .../rules/cfn/cloudtrail/inputs/valid_target_infra.rego | 4 ++-- .../rules/cfn/ebs/inputs/volume_encryption_infra.rego | 4 ++-- rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego | 4 ++-- rego/tests/rules/cfn/iam/inputs/policy_infra.rego | 4 ++-- rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego | 4 ++-- .../rules/cfn/lambda/inputs/empty_template_infra.rego | 4 ++-- .../lambda/inputs/invalid_function_not_public_infra.rego | 4 ++-- .../inputs/invalid_function_not_public_sam_infra.rego | 4 ++-- .../invalid_function_not_public_with_valid_infra.rego | 4 ++-- ...alid_function_not_public_account_permission_infra.rego | 4 ++-- ..._function_not_public_account_permission_sam_infra.rego | 4 ++-- .../lambda/inputs/valid_function_not_public_infra.rego | 4 ++-- .../inputs/valid_function_not_public_sam_infra.rego | 4 ++-- ...alid_function_not_public_service_permission_infra.rego | 4 ++-- ..._function_not_public_service_permission_sam_infra.rego | 4 ++-- rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego | 4 ++-- .../cfn/s3/inputs/invalid_block_public_access_infra.rego | 4 ++-- ...d_cloudtrail_s3_data_logging_all_one_bucket_infra.rego | 4 ++-- ...nvalid_cloudtrail_s3_data_logging_no_trails_infra.rego | 4 ++-- ..._cloudtrail_s3_data_logging_read_one_bucket_infra.rego | 4 ++-- ...dtrail_s3_data_logging_trail_no_data_events_infra.rego | 4 ++-- ...loudtrail_s3_data_logging_trail_no_selector_infra.rego | 4 ++-- ...cloudtrail_s3_data_logging_write_one_bucket_infra.rego | 4 ++-- ...l_s3_data_logging_write_one_bucket_read_all_infra.rego | 4 ++-- .../cfn/s3/inputs/invalid_encryption_missing_infra.rego | 4 ++-- .../s3/inputs/invalid_encryption_with_valid_infra.rego | 4 ++-- .../inputs/invalid_https_access_bucket_policy_infra.rego | 4 ++-- rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego | 4 ++-- .../cfn/s3/inputs/valid_block_public_access_infra.rego | 4 ++-- ..._cloudtrail_s3_data_logging_all_all_buckets_infra.rego | 4 ++-- ..._cloudtrail_s3_data_logging_all_two_buckets_infra.rego | 4 ++-- ...cloudtrail_s3_data_logging_read_all_buckets_infra.rego | 4 ++-- ...loudtrail_s3_data_logging_write_all_buckets_infra.rego | 4 ++-- ...l_s3_data_logging_write_one_bucket_read_all_infra.rego | 4 ++-- .../tests/rules/cfn/s3/inputs/valid_encryption_infra.rego | 4 ++-- .../s3/inputs/valid_https_access_bucket_policy_infra.rego | 4 ++-- .../cfn/vpc/inputs/default_security_group_infra.rego | 4 ++-- .../rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego | 4 ++-- rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego | 4 ++-- rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego | 4 ++-- .../tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego | 4 ++-- .../rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego | 4 ++-- .../aws/cloudfront/inputs/distribution_https_infra.rego | 4 ++-- .../aws/cloudtrail/inputs/log_file_validation_infra.rego | 4 ++-- .../rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego | 4 ++-- .../tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego | 4 ++-- .../tf/aws/iam/inputs/user_attached_policy_infra.rego | 4 ++-- .../tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego | 4 ++-- rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego | 4 ++-- .../aws/security_group/inputs/ingress_anywhere_infra.rego | 4 ++-- .../security_group/inputs/ingress_anywhere_rdp_infra.rego | 4 ++-- .../security_group/inputs/ingress_anywhere_ssh_infra.rego | 4 ++-- rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego | 4 ++-- .../inputs/security_group_no_inbound_22_infra.rego | 4 ++-- .../inputs/security_group_no_inbound_3389_infra.rego | 4 ++-- .../azurerm/sql/inputs/firewall_no_inbound_all_infra.rego | 4 ++-- .../azurerm/storage/inputs/account_deny_access_infra.rego | 4 ++-- .../storage/inputs/account_microsoft_services_infra.rego | 4 ++-- .../storage/inputs/account_secure_transfer_infra.rego | 4 ++-- .../storage/inputs/container_private_access_infra.rego | 4 ++-- .../compute/inputs/firewall_no_ingress_22_infra.rego | 4 ++-- .../compute/inputs/firewall_no_ingress_3389_infra.rego | 4 ++-- .../compute/inputs/subnet_flow_log_enabled_infra.rego | 4 ++-- .../inputs/subnet_private_google_access_infra.rego | 4 ++-- .../tf/google/kms/inputs/cryptokey_rotate_infra.rego | 4 ++-- 100 files changed, 201 insertions(+), 201 deletions(-) rename rego/lib/{test_helper.rego => fugue/regula/tests.rego} (86%) diff --git a/rego/lib/test_helper.rego b/rego/lib/fugue/regula/tests.rego similarity index 86% rename from rego/lib/test_helper.rego rename to rego/lib/fugue/regula/tests.rego index 966f82b4..7e954c3e 100644 --- a/rego/lib/test_helper.rego +++ b/rego/lib/fugue/regula/tests.rego @@ -1,4 +1,4 @@ -package regula +package fugue.regula.tests import data.fugue.resource_view.resource_view_input diff --git a/rego/scripts/generate-test-inputs.sh b/rego/scripts/generate-test-inputs.sh index c164b533..799e7820 100755 --- a/rego/scripts/generate-test-inputs.sh +++ b/rego/scripts/generate-test-inputs.sh @@ -76,10 +76,10 @@ function generate_tf_input { # - mock_config: The raw config input as its parsed by regula package ${package} -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("${plan_json_basename}", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources EOF } @@ -98,10 +98,10 @@ function generate_cfn_input { # - mock_config: The raw config input as its parsed by regula package ${package} -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("${config_basename}", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources EOF } diff --git a/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego index c494cb1d..982ad563 100644 --- a/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego +++ b/rego/tests/examples/aws/inputs/ec2_t2_only_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.ec2_t2_only_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("ec2_t2_only_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/examples/aws/inputs/iam_password_length_infra.rego b/rego/tests/examples/aws/inputs/iam_password_length_infra.rego index 20f0699f..9471094a 100644 --- a/rego/tests/examples/aws/inputs/iam_password_length_infra.rego +++ b/rego/tests/examples/aws/inputs/iam_password_length_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.iam_password_length_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("iam_password_length_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego b/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego index 6b93500a..c13c2902 100644 --- a/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego +++ b/rego/tests/examples/aws/inputs/tag_all_resources_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.tag_all_resources_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("tag_all_resources_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/examples/aws/inputs/useast1_only_infra.rego b/rego/tests/examples/aws/inputs/useast1_only_infra.rego index ef1d76ba..aed4c2ff 100644 --- a/rego/tests/examples/aws/inputs/useast1_only_infra.rego +++ b/rego/tests/examples/aws/inputs/useast1_only_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.examples.aws.inputs.useast1_only_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("useast1_only_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/invalid_encryption_infra.rego b/rego/tests/lib/inputs/invalid_encryption_infra.rego index 4bda1862..d3f8399d 100644 --- a/rego/tests/lib/inputs/invalid_encryption_infra.rego +++ b/rego/tests/lib/inputs/invalid_encryption_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.invalid_encryption_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_encryption_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_01_infra.rego b/rego/tests/lib/inputs/resource_view_01_infra.rego index 7ef431f1..a5a3c96d 100644 --- a/rego/tests/lib/inputs/resource_view_01_infra.rego +++ b/rego/tests/lib/inputs/resource_view_01_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_01_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("resource_view_01_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_02_infra.rego b/rego/tests/lib/inputs/resource_view_02_infra.rego index c4c75c53..b4583549 100644 --- a/rego/tests/lib/inputs/resource_view_02_infra.rego +++ b/rego/tests/lib/inputs/resource_view_02_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_02_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("resource_view_02_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_03_infra.rego b/rego/tests/lib/inputs/resource_view_03_infra.rego index b28a399b..13f8a9a6 100644 --- a/rego/tests/lib/inputs/resource_view_03_infra.rego +++ b/rego/tests/lib/inputs/resource_view_03_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_03_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("resource_view_03_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/resource_view_04_infra.rego b/rego/tests/lib/inputs/resource_view_04_infra.rego index f973caf5..154696ee 100644 --- a/rego/tests/lib/inputs/resource_view_04_infra.rego +++ b/rego/tests/lib/inputs/resource_view_04_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.resource_view_04_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("resource_view_04_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/valid_encryption_infra.rego b/rego/tests/lib/inputs/valid_encryption_infra.rego index 8500787c..b3cfbd3a 100644 --- a/rego/tests/lib/inputs/valid_encryption_infra.rego +++ b/rego/tests/lib/inputs/valid_encryption_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.valid_encryption_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_encryption_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/lib/inputs/volume_encrypted_infra.rego b/rego/tests/lib/inputs/volume_encrypted_infra.rego index d2dc52f6..5283d5c0 100644 --- a/rego/tests/lib/inputs/volume_encrypted_infra.rego +++ b/rego/tests/lib/inputs/volume_encrypted_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.lib.inputs.volume_encrypted_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("volume_encrypted_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego index 46848ff2..8262f53e 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_classic_custom_domain_name_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_classic_custom_domain_name_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego index a0803da1..558e9a46 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_classic_custom_domain_name_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_classic_custom_domain_name_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_classic_custom_domain_name_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego index f35649b2..35b52e17 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_v2_custom_domain_name_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_v2_custom_domain_name_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego index 3c91f9d9..d32c1fc6 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/invalid_v2_custom_domain_name_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.invalid_v2_custom_domain_name_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_v2_custom_domain_name_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego index 01bc7f32..fbe5a629 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_classic_custom_domain_name_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_classic_custom_domain_name_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego index e001d5ad..c56dcb72 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_classic_custom_domain_name_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_classic_custom_domain_name_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_classic_custom_domain_name_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego index 6a8dfdfc..94e2cef7 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_v2_custom_domain_name_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_v2_custom_domain_name_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego index 5f994100..edaecdd9 100644 --- a/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego +++ b/rego/tests/rules/cfn/api_gateway/inputs/valid_v2_custom_domain_name_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.api_gateway.inputs.valid_v2_custom_domain_name_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_v2_custom_domain_name_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego index ff0e20a6..ab74a97e 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/empty_template_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.empty_template_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("empty_template_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego index c1d9f25d..6f83b7ce 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_cloudwatch_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudwatch_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego index 9e5dadc4..43fbe73e 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_cloudwatch_with_valid_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_cloudwatch_with_valid_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudwatch_with_valid_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego index 3cb1245b..b51394b5 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_encryption_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_encryption_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_encryption_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego index 67e289d4..3e672368 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_log_validation_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_log_validation_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego index 4494d164..88f37748 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_log_validation_with_valid_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_log_validation_with_valid_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_log_validation_with_valid_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego index 0c218e0b..09e2ef38 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_s3_access_logging_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_s3_access_logging_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_s3_access_logging_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego index a5eba5bf..dd211ea2 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_target_public_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_target_public_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego index 6a3896b7..4d60ce26 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/invalid_target_public_write_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.invalid_target_public_write_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_target_public_write_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego index d0a8b893..66d3ef4c 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_cloudwatch_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_cloudwatch_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_cloudwatch_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego index c3cd3bb5..b7a624e9 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_encryption_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_encryption_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_encryption_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego index ff2bd881..5d643cc3 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_log_validation_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_log_validation_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_log_validation_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego index b156dc45..319af530 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_s3_access_logging_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_s3_access_logging_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_s3_access_logging_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego index dfe6e163..2ab94f6f 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_full_check_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_target_full_check_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_target_full_check_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego index 90a1001b..12393640 100644 --- a/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego +++ b/rego/tests/rules/cfn/cloudtrail/inputs/valid_target_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.cloudtrail.inputs.valid_target_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_target_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego b/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego index 79046ec2..cb5bfb88 100644 --- a/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego +++ b/rego/tests/rules/cfn/ebs/inputs/volume_encryption_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.ebs.inputs.volume_encryption_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("volume_encryption_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego b/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego index f640cc5f..afc201d9 100644 --- a/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego +++ b/rego/tests/rules/cfn/iam/inputs/admin_policy_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.iam.inputs.admin_policy_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("admin_policy_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/iam/inputs/policy_infra.rego b/rego/tests/rules/cfn/iam/inputs/policy_infra.rego index ce6d6f39..c9e55b8c 100644 --- a/rego/tests/rules/cfn/iam/inputs/policy_infra.rego +++ b/rego/tests/rules/cfn/iam/inputs/policy_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.iam.inputs.policy_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("policy_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego b/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego index 10c0387a..58faba1f 100644 --- a/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego +++ b/rego/tests/rules/cfn/kms/inputs/key_rotation_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.kms.inputs.key_rotation_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("key_rotation_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego index a7e85017..cf86c35b 100644 --- a/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/empty_template_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.empty_template_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("empty_template_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego index 27fb7275..84743268 100644 --- a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.invalid_function_not_public_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_function_not_public_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego index bd69b534..941a1e26 100644 --- a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.invalid_function_not_public_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_function_not_public_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego index b23b88e4..56f29b1d 100644 --- a/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/invalid_function_not_public_with_valid_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.invalid_function_not_public_with_valid_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_function_not_public_with_valid_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego index c8d97f3d..2f02a0c1 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_account_permission_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_function_not_public_account_permission_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego index cd108292..1b7c200f 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_account_permission_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_account_permission_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_function_not_public_account_permission_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego index 771fe8b9..70897503 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_function_not_public_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego index a07c29ec..1e743128 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_function_not_public_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego index 86eddb49..e270a6ba 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_service_permission_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_function_not_public_service_permission_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego index 939a0255..6041ff95 100644 --- a/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego +++ b/rego/tests/rules/cfn/lambda/inputs/valid_function_not_public_service_permission_sam_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.lambda.inputs.valid_function_not_public_service_permission_sam_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_function_not_public_service_permission_sam_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego b/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego index 97f1cf59..bdb9ad74 100644 --- a/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/empty_template_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.empty_template_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("empty_template_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego index 4a279f6f..ea20053b 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_block_public_access_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_block_public_access_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_block_public_access_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego index df624d32..67be9a41 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_all_one_bucket_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_all_one_bucket_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego index 1c29d389..778b7662 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_no_trails_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_no_trails_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_no_trails_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego index 4d0129c2..02c41fa7 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_read_one_bucket_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_read_one_bucket_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego index cbe86ca6..6de6356d 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_trail_no_data_events_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego index c4fc7289..508a1ba2 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_trail_no_selector_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_trail_no_selector_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego index b4b9b237..893f458d 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_write_one_bucket_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_write_one_bucket_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego index 06cf527a..7cd61727 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego index 117ca3b5..e7ee6534 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_missing_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_encryption_missing_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_encryption_missing_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego index 9b19b105..1c6ba5c4 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_encryption_with_valid_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_encryption_with_valid_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_encryption_with_valid_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego index 13542a6e..c6874978 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_https_access_bucket_policy_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_https_access_bucket_policy_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_https_access_bucket_policy_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego b/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego index 75558a12..cfe367dc 100644 --- a/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/invalid_missing_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.invalid_missing_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("invalid_missing_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego index fa8ac461..30b717e5 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_block_public_access_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_block_public_access_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_block_public_access_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego index 71896519..ca9cf63b 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_all_buckets_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_all_all_buckets_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_all_all_buckets_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego index d1493fc2..bc9ce557 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_all_two_buckets_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_all_two_buckets_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_all_two_buckets_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego index 21333bff..750c7113 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_read_all_buckets_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_read_all_buckets_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_read_all_buckets_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego index c6015f12..7af7d4f2 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_all_buckets_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_write_all_buckets_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_write_all_buckets_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego index a6a81751..b46fad73 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_cloudtrail_s3_data_logging_write_one_bucket_read_all_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego index 431d0aa0..4873a0a8 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_encryption_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_encryption_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_encryption_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego b/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego index 7f24dab1..76d0ab6b 100644 --- a/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego +++ b/rego/tests/rules/cfn/s3/inputs/valid_https_access_bucket_policy_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.s3.inputs.valid_https_access_bucket_policy_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("valid_https_access_bucket_policy_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego b/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego index cc26b60d..44729fd6 100644 --- a/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/default_security_group_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.default_security_group_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("default_security_group_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego b/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego index ecaefe62..a71e82bb 100644 --- a/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/flow_logging_enabled_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.flow_logging_enabled_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("flow_logging_enabled_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego b/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego index 27ebc998..78d7dcdc 100644 --- a/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/ingress_22_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.ingress_22_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("ingress_22_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego b/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego index 314ea63d..03b0f52a 100644 --- a/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/ingress_3389_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.ingress_3389_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("ingress_3389_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego index 0339cd42..cb133f45 100644 --- a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_22_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.nacl_ingress_22_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("nacl_ingress_22_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego index dd1dc756..9e7a8270 100644 --- a/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego +++ b/rego/tests/rules/cfn/vpc/inputs/nacl_ingress_3389_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.cfn.vpc.inputs.nacl_ingress_3389_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("nacl_ingress_3389_infra.cfn", "cfn") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego index 20bcdc42..b4d99ba4 100644 --- a/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego +++ b/rego/tests/rules/tf/aws/cloudfront/inputs/distribution_https_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.cloudfront.inputs.distribution_https_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("distribution_https_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego index 421e7afe..2795aace 100644 --- a/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego +++ b/rego/tests/rules/tf/aws/cloudtrail/inputs/log_file_validation_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.cloudtrail.inputs.log_file_validation_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("log_file_validation_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego index fb1db2bf..2eac2bf0 100644 --- a/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego +++ b/rego/tests/rules/tf/aws/ebs/inputs/volume_encrypted_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.ebs.inputs.volume_encrypted_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("volume_encrypted_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego index 3b7edcde..32cacdd6 100644 --- a/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego +++ b/rego/tests/rules/tf/aws/iam/inputs/admin_policy_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.iam.inputs.admin_policy_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("admin_policy_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego index b62e7016..0ed6e8c7 100644 --- a/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego +++ b/rego/tests/rules/tf/aws/iam/inputs/user_attached_policy_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.iam.inputs.user_attached_policy_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("user_attached_policy_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego index fc25a2ee..8eb69e20 100644 --- a/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego +++ b/rego/tests/rules/tf/aws/kms/inputs/key_rotation_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.kms.inputs.key_rotation_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("key_rotation_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego index e871c057..df60e402 100644 --- a/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego +++ b/rego/tests/rules/tf/aws/s3/inputs/bucket_sse_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.s3.inputs.bucket_sse_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("bucket_sse_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego index 55b3290e..bfe0690c 100644 --- a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.security_group.inputs.ingress_anywhere_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("ingress_anywhere_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego index 8acbf719..7d78b934 100644 --- a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_rdp_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.security_group.inputs.ingress_anywhere_rdp_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("ingress_anywhere_rdp_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego index ce257259..35100b66 100644 --- a/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego +++ b/rego/tests/rules/tf/aws/security_group/inputs/ingress_anywhere_ssh_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.security_group.inputs.ingress_anywhere_ssh_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("ingress_anywhere_ssh_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego index 784a5922..5fce77c0 100644 --- a/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego +++ b/rego/tests/rules/tf/aws/vpc/inputs/flow_log_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.aws.vpc.inputs.flow_log_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("flow_log_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego index 3dcb543e..8da7634a 100644 --- a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego +++ b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_22_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.network.inputs.security_group_no_inbound_22_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("security_group_no_inbound_22_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego index 2bcefba9..e0f0d0c9 100644 --- a/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego +++ b/rego/tests/rules/tf/azurerm/network/inputs/security_group_no_inbound_3389_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.network.inputs.security_group_no_inbound_3389_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("security_group_no_inbound_3389_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego index 4329c673..438eedd8 100644 --- a/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego +++ b/rego/tests/rules/tf/azurerm/sql/inputs/firewall_no_inbound_all_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.sql.inputs.firewall_no_inbound_all_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("firewall_no_inbound_all_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego index d7844a2c..0b4f3acb 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_deny_access_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.account_deny_access_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("account_deny_access_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego index 73cd5cbd..3587a544 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_microsoft_services_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.account_microsoft_services_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("account_microsoft_services_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego index 6abe8d34..73654574 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/account_secure_transfer_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.account_secure_transfer_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("account_secure_transfer_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego index bd558f1c..0c847b33 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego +++ b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.azurerm.storage.inputs.container_private_access_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("container_private_access_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego index 543e9217..cb2b46d6 100644 --- a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_22_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.firewall_no_ingress_22_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("firewall_no_ingress_22_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego index 41819093..32fd21e2 100644 --- a/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/firewall_no_ingress_3389_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.firewall_no_ingress_3389_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("firewall_no_ingress_3389_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego index 350dff57..d44d78e8 100644 --- a/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/subnet_flow_log_enabled_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.subnet_flow_log_enabled_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("subnet_flow_log_enabled_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego index 6c4331da..f948a70c 100644 --- a/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego +++ b/rego/tests/rules/tf/google/compute/inputs/subnet_private_google_access_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.compute.inputs.subnet_private_google_access_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("subnet_private_google_access_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources diff --git a/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego index f16dc51a..52f21944 100644 --- a/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego +++ b/rego/tests/rules/tf/google/kms/inputs/cryptokey_rotate_infra.rego @@ -25,8 +25,8 @@ # - mock_config: The raw config input as its parsed by regula package tests.rules.tf.google.kms.inputs.cryptokey_rotate_infra -import data.regula +import data.fugue.regula.tests mock_config := regula_load_type("cryptokey_rotate_infra.tfplan", "tf-plan") -mock_input := regula.mock_input(mock_config) +mock_input := tests.mock_input(mock_config) mock_resources := mock_input.resources From 4b0dad5e01370a84f9ac0781923c663db79815cc Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 18 May 2021 14:59:03 -0400 Subject: [PATCH 54/65] [RM-5352] Account for multiple configs in mock_input() --- rego/lib/fugue/regula/tests.rego | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rego/lib/fugue/regula/tests.rego b/rego/lib/fugue/regula/tests.rego index 7e954c3e..c88c23f3 100644 --- a/rego/lib/fugue/regula/tests.rego +++ b/rego/lib/fugue/regula/tests.rego @@ -3,6 +3,8 @@ package fugue.regula.tests import data.fugue.resource_view.resource_view_input mock_input(iac_configs) = ret { - iac_config = iac_configs[_] + is_array(iac_configs) + count(iac_configs) > 0 + iac_config = iac_configs[0] ret = resource_view_input with input as iac_config.content } From 00e3f0c41576133efd339a88eb6ca8fea2863911 Mon Sep 17 00:00:00 2001 From: Jason Lynch Date: Tue, 18 May 2021 14:59:20 -0400 Subject: [PATCH 55/65] [RM-5352] Remove TAP13 reporter enum --- pkg/reporter/base.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/reporter/base.go b/pkg/reporter/base.go index 93c3cd7f..3b8f34c7 100644 --- a/pkg/reporter/base.go +++ b/pkg/reporter/base.go @@ -51,7 +51,6 @@ const ( Table Junit Tap - Tap13 None ) @@ -60,7 +59,6 @@ var FormatIds = map[Format][]string{ Table: {"table"}, Junit: {"junit"}, Tap: {"tap"}, - Tap13: {"tap13"}, None: {"none"}, } From b225cc8c13f391676ae6db2b4151b03a1bfe0d46 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Tue, 18 May 2021 09:35:42 +0000 Subject: [PATCH 56/65] [RM-5431] HCL Support in Regula CLI --- .gitmodules | 6 + cmd/run.txt | 1 + cmd/show.go | 77 ++ go.mod | 18 +- go.sum | 773 +++++++++++++++++- main.go | 2 + pkg/loader/base.go | 7 + pkg/loader/loadpaths.go | 3 + pkg/loader/tf.go | 700 ++++++++++++++++ pkg/tf_resource_schemas/generate/main.go | 89 ++ pkg/tf_resource_schemas/go.mod | 3 + pkg/tf_resource_schemas/load.go | 24 + pkg/tf_resource_schemas/resource_schemas.json | 1 + pkg/tf_resource_schemas/types.go | 42 + providers/terraform-provider-aws | 1 + providers/terraform-provider-google | 1 + rego/lib/fugue/input_type.rego | 2 + rego/lib/fugue/resource_view.rego | 19 +- .../tf/aws/iam/user_attached_policy.rego | 4 +- .../compute/subnet_flow_log_enabled.rego | 6 +- .../container_private_access_test.rego | 1 - .../inputs/container_private_access_infra.tf | 5 - .../compute/subnet_flow_log_enabled_test.rego | 6 +- tools.go | 11 + 24 files changed, 1775 insertions(+), 27 deletions(-) create mode 100644 .gitmodules create mode 100644 cmd/show.go create mode 100644 pkg/loader/tf.go create mode 100644 pkg/tf_resource_schemas/generate/main.go create mode 100644 pkg/tf_resource_schemas/go.mod create mode 100644 pkg/tf_resource_schemas/load.go create mode 100644 pkg/tf_resource_schemas/resource_schemas.json create mode 100644 pkg/tf_resource_schemas/types.go create mode 160000 providers/terraform-provider-aws create mode 160000 providers/terraform-provider-google create mode 100644 tools.go diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..d78feb04 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "providers/terraform-provider-google"] + path = providers/terraform-provider-google + url = https://github.com/hashicorp/terraform-provider-google.git +[submodule "providers/terraform-provider-aws"] + path = providers/terraform-provider-aws + url = https://github.com/hashicorp/terraform-provider-aws.git diff --git a/cmd/run.txt b/cmd/run.txt index 1195c71d..ea2103af 100644 --- a/cmd/run.txt +++ b/cmd/run.txt @@ -13,6 +13,7 @@ Input types: auto Automatically determine input types (default) tf-plan Terraform plan JSON cfn CloudFormation template in YAML or JSON format + tf Terraform directory or file Output formats: json A JSON report containing rule results and a summary (default) diff --git a/cmd/show.go b/cmd/show.go new file mode 100644 index 00000000..cce90d19 --- /dev/null +++ b/cmd/show.go @@ -0,0 +1,77 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/fugue/regula/pkg/loader" + "github.com/spf13/cobra" + "github.com/thediveo/enumflag" +) + +func NewShowCommand() *cobra.Command { + var inputType loader.InputType + + cmd := &cobra.Command{ + Use: "show [item]", + Short: "Show debug information.", + Long: `Show debug information. Currently the available items are: + input [file..] Show the JSON input being passed to regula`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + fmt.Fprintf(os.Stderr, "Expected an item to show\n") + os.Exit(1) + } + + switch args[0] { + case "input": + paths := args[1:] + loadedFiles, err := loader.LoadPaths(loader.LoadPathsOptions{ + Paths: paths, + InputType: inputType, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + + bytes, err := json.MarshalIndent(loadedFiles.RegulaInput(), "", " ") + if err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } + fmt.Println(string(bytes)) + + default: + fmt.Fprintf(os.Stderr, "Unknown item: %s\n", args[0]) + os.Exit(1) + } + }, + } + + cmd.Flags().VarP( + enumflag.New(&inputType, "input-type", loader.InputTypeIDs, enumflag.EnumCaseInsensitive), + "input-type", "t", + "Set the input type for the given paths") + + return cmd +} + +func init() { + rootCmd.AddCommand(NewShowCommand()) +} diff --git a/go.mod b/go.mod index 11602277..6e9e6ae1 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,27 @@ go 1.16 require ( github.com/alexeyco/simpletable v1.0.0 - github.com/fatih/color v1.7.0 + github.com/fatih/color v1.9.0 github.com/go-git/go-billy/v5 v5.3.1 // indirect github.com/go-git/go-git/v5 v5.3.0 github.com/golang/mock v1.5.0 + github.com/hashicorp/hcl/v2 v2.10.0 + github.com/hashicorp/terraform v0.15.1 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.5.0 + github.com/hashicorp/terraform-provider-google v1.20.0 // indirect github.com/open-policy-agent/opa v0.28.0 github.com/spf13/cobra v1.1.3 - github.com/stretchr/testify v1.4.0 + github.com/stretchr/testify v1.6.1 + github.com/terraform-providers/terraform-provider-aws v1.60.0 + github.com/terraform-providers/terraform-provider-google v1.20.0 github.com/thediveo/enumflag v0.10.1 + github.com/zclconf/go-cty v1.8.2 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c + tf_resource_schemas v0.0.0-00010101000000-000000000000 +) + +replace ( + github.com/terraform-providers/terraform-provider-aws => ./providers/terraform-provider-aws + github.com/terraform-providers/terraform-provider-google => ./providers/terraform-provider-google + tf_resource_schemas => ./pkg/tf_resource_schemas/ ) diff --git a/go.sum b/go.sum index 94abe689..cd4f687b 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +bitbucket.org/creachadair/stringset v0.0.8 h1:gQqe4vs8XWgMyijfyKE6K8o4TcyGGrRXe0JvHgx5H+M= +bitbucket.org/creachadair/stringset v0.0.8/go.mod h1:AgthVMyMxC/6FK1KBJ2ALdqkZObGN8hOetgpwXyMn34= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -5,24 +7,98 @@ cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6A cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.60.0/go.mod h1:yw2G51M9IfRboUH61Us8GqCeF1PzPblB823Mn2q2eAU= +cloud.google.com/go v0.61.0/go.mod h1:XukKJg4Y7QsUu0Hxg3qQKUWR4VuWivmyMK2+rUyxAqw= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0 h1:oKpsiyKMfVpwR3zSAkQixGzlVE5ovitBuO0qSmCf0bI= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/bigtable v1.5.0 h1:ylPDE1w1+koWpPOzf8HkX2PqKaIvN8hPc9t+F0GT3do= +cloud.google.com/go/bigtable v1.5.0/go.mod h1:713PsD2nkJwTioSe6vF/sFCAcjhINJ62cEtKCr8u+F8= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v45.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v47.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v51.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go v52.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.3/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest v0.11.10/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.0/go.mod h1:JljT387FplPzBA31vUcvsetLKF3pec5bdAxjVU4kI2s= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= +github.com/Azure/go-autorest/autorest/validation v0.3.0/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/ChrisTrenkamp/goxpath v0.0.0-20190607011252-c5096ec8773d/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4= +github.com/GoogleCloudPlatform/declarative-resource-client-library v0.0.0-20210405181318-9364c5bf716b h1:c12WCdB689IkCc0BpR4g97jN6A7FrSAdEOxqdVTy+FI= +github.com/GoogleCloudPlatform/declarative-resource-client-library v0.0.0-20210405181318-9364c5bf716b/go.mod h1:oEeBHikdF/NrnUy0ornVaY1OT+jGvTqm+LQS0+ZDKzU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= +github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 h1:w1UutsfOrms1J05zt7ISrnJIXKzwaspym5BTKGx93EI= +github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -31,132 +107,307 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alexeyco/simpletable v1.0.0 h1:ZQ+LvJ4bmoeHb+dclF64d0LX+7QAi7awsfCrptZrpHk= github.com/alexeyco/simpletable v1.0.0/go.mod h1:VJWVTtGUnW7EKbMRH8cE13SigKGx/1fO2SeeOiGeBkk= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw= +github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-shquot v0.0.1/go.mod h1:lw58XsE5IgUXZ9h0cxnypdx31p9mPFIVEQ9P3c7MlrU= +github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13/go.mod h1:7kfpUbyCdGJ9fDRCp3fopPQi5+cKNHgTE4ZuNrO71Cw= +github.com/apparentlymart/go-versions v1.0.1 h1:ECIpSn0adcYNsBfSRwdDdz9fWlL+S/6EUd9+irwkBgU= +github.com/apparentlymart/go-versions v1.0.1/go.mod h1:YF5j7IQtrOAOnsGkniupEA5bfCjzd7i14yu0shZavyM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= +github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.38.19 h1:eg7LfiWRNYjbeS+w2+lHwZOKIgnh0NdYr6LkakZ112Y= +github.com/aws/aws-sdk-go v1.38.19/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= +github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= +github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bytecodealliance/wasmtime-go v0.26.0 h1:wHOt9u+irLBCUjotanqDwVbnNmTJ1gWQxY2+q+XeMp4= github.com/bytecodealliance/wasmtime-go v0.26.0/go.mod h1:q320gUxqyI8yB+ZqRuaJOEnGkAnHh6WtJjMaT2CW4wI= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creachadair/staticfile v0.1.2/go.mod h1:a3qySzCIXEprDGxk6tSxSI+dBBdLzqeBOMhZ+o2d3pM= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= +github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1/go.mod h1:lcy9/2gH1jn/VCLouHA6tOEwLoNVd4GW6zhuKLmHC2Y= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gammazero/deque v0.0.0-20180920172122-f6adf94963e4 h1:R+19WKQClnfMXS60cP5BmMe1wjZ4u0evY2p2Ar0ZTXo= +github.com/gammazero/deque v0.0.0-20180920172122-f6adf94963e4/go.mod h1:GeIq9qoE43YdGnDXURnmKTnGg15pQz4mYkXSTChbneI= +github.com/gammazero/workerpool v0.0.0-20181230203049-86a96b5d5d92 h1:EipXK6U05IQ2wtuFRn4k3h0+2lXypzItoXGVyf4r9Io= +github.com/gammazero/workerpool v0.0.0-20181230203049-86a96b5d5d92/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.1.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM= github.com/go-git/go-git/v5 v5.3.0 h1:8WKMtJR2j8RntEXR/uvTKagfEt4GYlwQ7mntE4+0GWc= github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4X+lNVprw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= +github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= +github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= +github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= +github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= +github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= +github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= +github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= +github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= +github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= +github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= +github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= +github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= +github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= +github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= +github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM= +github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= +github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= +github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= +github.com/golangci/golangci-lint v1.18.0/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg= +github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU= +github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= +github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= +github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= +github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= +github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= +github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= +github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.6.1-0.20191122030953-d8ac278c1c9d/go.mod h1:ozGNgr9KYOVATV5jsgHl/ceCDXGuguqOZAzoQ/2vcNM= +github.com/gophercloud/gophercloud v0.10.1-0.20200424014253-c3bfe50899e5/go.mod h1:gmC5oQqMDOMO1t1gq5DquX/yAU808e/4mzjjDA76+Ss= +github.com/gophercloud/utils v0.0.0-20200423144003-7c72efc7435d/go.mod h1:ehWUbLQJPqS0Ep+CxeD559hsm9pthPXadJNKwZkp43w= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -164,58 +415,171 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/aws-sdk-go-base v0.6.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= +github.com/hashicorp/aws-sdk-go-base v0.7.1 h1:7s/aR3hFn74tYPVihzDyZe7y/+BorN70rr9ZvpV3j3o= +github.com/hashicorp/aws-sdk-go-base v0.7.1/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= +github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-azure-helpers v0.12.0/go.mod h1:Zc3v4DNeX6PDdy7NljlYpnrdac1++qNW0I4U+ofGwpg= +github.com/hashicorp/go-azure-helpers v0.14.0/go.mod h1:kR7+sTDEb9TOp/O80ss1UEJg1t4/BHLD/U8wHLS4BGQ= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= +github.com/hashicorp/go-getter v1.5.0/go.mod h1:a7z7NPPfNQpJWcn4rSWFtdrSldqLdLPEF3d8nFMsSLM= +github.com/hashicorp/go-getter v1.5.1 h1:lM9sM02nvEApQGFgkXxWbhfqtyN+AyhQmi+MaMdBDOI= +github.com/hashicorp/go-getter v1.5.1/go.mod h1:a7z7NPPfNQpJWcn4rSWFtdrSldqLdLPEF3d8nFMsSLM= +github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk= +github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= +github.com/hashicorp/go-plugin v1.4.0/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.4.1 h1:6UltRQlLN9iZO513VveELp5xyaFxVD2+1OVylE+2E+w= +github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-retryablehttp v0.5.2 h1:AoISa4P4IsW0/m4T6St8Yw38gTl5GtBAgfkhYh1xAz4= +github.com/hashicorp/go-retryablehttp v0.5.2/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= +github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= +github.com/hashicorp/go-slug v0.4.1/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= +github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-tfe v0.8.1/go.mod h1:XAV72S4O1iP8BDaqiaPLmL2B4EE6almocnOn8E8stHc= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= +github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90= +github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= +github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= +github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/hashicorp/hcl/v2 v2.10.0 h1:1S1UnuhDGlv3gRFV4+0EdwB+znNP5HmcGbIqwnSCByg= +github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/terraform v0.15.1 h1:dfu1/x3kf8zOTi/zDX5HiOaCukj0n4XB8D7lSo2F8cU= +github.com/hashicorp/terraform v0.15.1/go.mod h1:i8pxtLjDNjiMELBM49hWs4ClAV00Fxtn2dfglLO+wDo= +github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2/go.mod h1:Z0Nnk4+3Cy89smEbrq+sl1bxc9198gIP4I7wcQF6Kqs= +github.com/hashicorp/terraform-exec v0.12.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8= +github.com/hashicorp/terraform-exec v0.13.0 h1:1Pth+pdWJAufJuWWjaVOVNEkoRTOjGn3hQpAqj4aPdg= +github.com/hashicorp/terraform-exec v0.13.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8= +github.com/hashicorp/terraform-json v0.8.0 h1:XObQ3PgqU52YLQKEaJ08QtUshAfN3yu4u8ebSW0vztc= +github.com/hashicorp/terraform-json v0.8.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE= +github.com/hashicorp/terraform-plugin-go v0.1.0/go.mod h1:10V6F3taeDWVAoLlkmArKttR3IULlRWFAGtQIQTIDr4= +github.com/hashicorp/terraform-plugin-go v0.2.1 h1:EW/R8bB2Zbkjmugzsy1d27yS8/0454b3MtYHkzOknqA= +github.com/hashicorp/terraform-plugin-go v0.2.1/go.mod h1:10V6F3taeDWVAoLlkmArKttR3IULlRWFAGtQIQTIDr4= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.4.0/go.mod h1:JBItawj+j8Ssla5Ib6BC/W9VQkOucBfnX7VRtyx1vw8= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.5.0 h1:4EHNOAjwiYCeBxY16rt2KwyRNNVsCaVO3kWBbiXfYM0= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.5.0/go.mod h1:z+cMZ0iswzZOahBJ3XmNWgWkVnAd2bl8g+FhyyuPDH4= +github.com/hashicorp/terraform-provider-google v1.20.0 h1:dVzBoqMHZA4PDAJaH3ztIey2cxFx6e+kRDAr3bMSrmI= +github.com/hashicorp/terraform-provider-google v1.20.0/go.mod h1:19QAcvJTh1z3BfW6cxR5MQd89aIurcIIur99oJGbv/E= +github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= +github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= +github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jen20/awspolicyequivalence v1.1.0 h1:cn37D6o0lXLwqx2neCokGfaB3LLNSo5CrLMLGjY609g= +github.com/jen20/awspolicyequivalence v1.1.0/go.mod h1:PV1fS2xyHhCLp83vbgSMFr2drM4GzG61wkz+k4pOG3E= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba h1:NARVGAAgEXvoMeNPHhPFt1SBt1VMznA3Gnz9d0qj+co= +github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -227,36 +591,98 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/likexian/gokit v0.0.0-20190309162924-0a377eecf7aa/go.mod h1:QdfYv6y6qPA9pbBA2qXtoT8BMKha6UyNbxWGWl/9Jfk= +github.com/likexian/gokit v0.0.0-20190418170008-ace88ad0983b/go.mod h1:KKqSnk/VVSW8kEyO2vVCXoanzEutKdlBAPohmGXkxCk= +github.com/likexian/gokit v0.0.0-20190501133040-e77ea8b19cdc/go.mod h1:3kvONayqCaj+UgrRZGpgfXzHdMYCAO0KAt4/8n0L57Y= +github.com/likexian/gokit v0.20.15/go.mod h1:kn+nTv3tqh6yhor9BC4Lfiu58SmH8NmQ2PmEl+uM6nU= +github.com/likexian/simplejson-go v0.0.0-20190409170913-40473a74d76d/go.mod h1:Typ1BfnATYtZ/+/shXfFYLrovhFyuKvzwrdOnIDHlmg= +github.com/likexian/simplejson-go v0.0.0-20190419151922-c1f9f0b4f084/go.mod h1:U4O1vIJvIKwbMZKUJ62lppfdvkCdVd2nfMimHK81eec= +github.com/likexian/simplejson-go v0.0.0-20190502021454-d8787b4bfa0b/go.mod h1:3BWwtmKP9cXWwYCr5bkoVDEfLywacOv0s06OBEDpyt8= +github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82/go.mod h1:y54tfGmO3NKssKveTEFFzH8C/akrSOy/iW9qEAUDV84= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= +github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEbxUJlNDEBNbdQMkPSp6yaKcRXVI6f4ddk8Riv4bc= +github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.1/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4= +github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-linereader v0.0.0-20190213213312-1b945b3263eb/go.mod h1:OaY7UOoTkkrX3wRwjpYRKafIkkyeD0UtweSHAWWiqQM= +github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.0.4/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE= +github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= +github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -264,18 +690,30 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758= +github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= @@ -290,23 +728,30 @@ github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxS github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d h1:zapSxdmZYY6vJWXFKLQ+MkI+agc+HQyfrCGowDSHiKs= github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20201207095918-0426ae3fba23/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= +github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs= +github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -334,41 +779,63 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= @@ -376,28 +843,68 @@ github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3 github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw= +github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4= +github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c/go.mod h1:wk2XFUg6egk4tSDNZtXeKfe2G6690UVyt163PuUxBZk= github.com/thediveo/enumflag v0.10.1 h1:DB3Ag69VZ7BCv6jzKECrZ0ebZrHLzFRMIFYt96s4OxM= github.com/thediveo/enumflag v0.10.1/go.mod h1:KyVhQUPzreSw85oJi2uSjFM0ODLKXBH0rPod7zc2pmI= +github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tombuildsstuff/giovanni v0.15.1/go.mod h1:0TZugJPEtqzPlMpuJHYfXY6Dq2uLPrXf98D2XQSxNbA= +github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= +github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY= github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.5.1/go.mod h1:nHzOclRkoj++EU9ZjSrZvRG0BXIWt8c7loYc0qXAFGQ= +github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.8.2 h1:u+xZfBKgpycDnTNjPhGiTEYZS5qS/Sb5MqSfm7vzcjg= +github.com/zclconf/go-cty v1.8.2/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= @@ -406,6 +913,11 @@ go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -413,23 +925,39 @@ go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhW go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191202143827-86a70503ff7e/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -439,22 +967,37 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -464,22 +1007,58 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93 h1:alLDrZkL34Y2bnGHfvC1CYBRBXCXgx8AC2vY4MRtYX4= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -489,65 +1068,145 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191203134012-c197fd4bf371/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200626171337-aa94e735be7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200721032237-77f530d86f9a/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -560,11 +1219,33 @@ google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.34.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0 h1:12aHIhhQCpWtd3Rcp2WwbboB5W72tJHcjzyA9MCoHAw= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -575,6 +1256,38 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200711021454-869866162049/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200721032028-5044d0edf986/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb h1:hcskBH5qZCOa7WpTUFUFvoebnSFZBYpjykLtjIp9DVk= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= @@ -584,13 +1297,35 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0 h1:o1bcQ6imQMIOpdrO3SWf2z5RV72WbDwdXuK0MDlc8As= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -599,10 +1334,14 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= @@ -614,6 +1353,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -625,6 +1366,28 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A= +k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA= +k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT68DBI8uEePRt89cSvoXUVqbkWHq4= +k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= +k8s.io/utils v0.0.0-20200411171748-3d5a2fe318e4/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= +mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= +mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= +rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/main.go b/main.go index 06b7c2c4..a506f01d 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,8 @@ import ( "github.com/fugue/regula/cmd" ) +//go:generate go run pkg/tf_resource_schemas/generate/main.go + func main() { cmd.Execute() } diff --git a/pkg/loader/base.go b/pkg/loader/base.go index 531f3465..bce8b469 100644 --- a/pkg/loader/base.go +++ b/pkg/loader/base.go @@ -38,6 +38,9 @@ const ( // Cfn means that regula will only look for CloudFormation template files in given // directories and it will assume that given files are CloudFormation YAML or JSON. Cfn + // Tf means that regula will load the HCL in the directory in a similar + // way to terraform plan, or it can also load individual files. + Tf ) // InputTypeIDs maps the InputType enums to string values that can be specified in @@ -46,6 +49,7 @@ var InputTypeIDs = map[InputType][]string{ Auto: {"auto"}, TfPlan: {"tf-plan"}, Cfn: {"cfn"}, + Tf: {"tf"}, } // InputTypeForString is a utility function to translate the string name of an input @@ -58,6 +62,8 @@ func InputTypeForString(typeStr string) (InputType, error) { return Cfn, nil case "tf-plan": return TfPlan, nil + case "tf": + return Tf, nil default: return -1, fmt.Errorf("Unrecognized input type %v", typeStr) } @@ -89,6 +95,7 @@ type IACConfiguration interface { // LoadedFiles are all of the files contained within this configuration. LoadedFiles() []string // Location resolves an attribute path to to a file, line and column. + // The first element of the attributePath is usually the resource ID. Location(attributePath []string) (*Location, error) } diff --git a/pkg/loader/loadpaths.go b/pkg/loader/loadpaths.go index 504e7116..478db2c0 100644 --- a/pkg/loader/loadpaths.go +++ b/pkg/loader/loadpaths.go @@ -182,11 +182,14 @@ func DetectorByInputType(inputType InputType) (ConfigurationDetector, error) { return NewAutoDetector( &CfnDetector{}, &TfPlanDetector{}, + &TfDetector{}, ), nil case Cfn: return &CfnDetector{}, nil case TfPlan: return &TfPlanDetector{}, nil + case Tf: + return &TfDetector{}, nil default: return nil, fmt.Errorf("Unsupported input type: %v", inputType) } diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go new file mode 100644 index 00000000..905c49f4 --- /dev/null +++ b/pkg/loader/tf.go @@ -0,0 +1,700 @@ +// Copyright 2021 Fugue, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package loader + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/terraform/configs" + "github.com/zclconf/go-cty/cty" + + "tf_resource_schemas" +) + +type TfDetector struct{} + +func (t *TfDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) { + if !strings.HasSuffix(i.Path(), ".tf") { + return nil, fmt.Errorf("Expected a .tf extension for %s", i.Path()) + } + + return parseFiles([]string{}, nil, []string{i.Path()}) +} + +func (t *TfDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) { + // First check that a `.tf` file exists in the directory. + if matches, err := filepath.Glob(i.Path() + "/*.tf"); err != nil || len(matches) == 0 { + return nil, fmt.Errorf("Directory does not contain a .tf file: %s", i.Path()) + } + + return parseDirectory([]string{}, i.Path()) +} + +type HclConfiguration struct { + // Path of the module. This indicates its position in the module tree. + // Example: `[]` for the root module, `["child1"]` for children. + path []string + + // Directory the configuration is loaded from. Necessary to find the + // locations of child modules. If this is `nil`, it means the configuration + // was not loaded from a directory. + dir *string + + // Filepaths that have been loaded. + filepaths []string + + // The actual HCL module. + module *configs.Module + + // A pointer to schemas we can use. + schemas tf_resource_schemas.ResourceSchemas + + // A map of loaded child modules. + children map[string]*HclConfiguration + + // Cached inputs (vars) for the child modules + childrenVars map[string]map[string]interface{} + + // Values of variables. Maybe we should make this lazy to handle cycles + // better? + vars map[string]interface{} +} + +func parseDirectory(path []string, dir string) (*HclConfiguration, error) { + parser := configs.NewParser(nil) + var diags hcl.Diagnostics + + primary, _, diags := parser.ConfigDirFiles(dir) + if diags.HasErrors() { + return nil, diags + } + + return parseFiles(path, &dir, primary) +} + +func parseFiles(path []string, dir *string, filepaths []string) (*HclConfiguration, error) { + // Attempt to make filepaths relative, since we have the directory as well. + for i := range filepaths { + if dir != nil { + if rel, err := filepath.Rel(*dir, filepaths[i]); err != nil { + filepaths[i] = rel + } + } + } + + configuration := new(HclConfiguration) + configuration.path = path + configuration.dir = dir + configuration.filepaths = filepaths + + parser := configs.NewParser(nil) + var diags hcl.Diagnostics + parsedFiles := make([]*configs.File, 0) + overrideFiles := make([]*configs.File, 0) + + for _, file := range filepaths { + f, fDiags := parser.LoadConfigFile(file) + diags = append(diags, fDiags...) + parsedFiles = append(parsedFiles, f) + } + module, lDiags := configs.NewModule(parsedFiles, overrideFiles) + configuration.module = module + diags = append(diags, lDiags...) + if diags.HasErrors() { + fmt.Fprintf(os.Stderr, "%s\n", diags.Error()) + } + if configuration.module == nil { + // Only actually throw an error if we don't have a module. We can + // still try and validate what we can. + return nil, fmt.Errorf(diags.Error()) + } + + configuration.schemas = tf_resource_schemas.LoadResourceSchemas() + + // Load children + configuration.children = make(map[string]*HclConfiguration) + if dir != nil { + for key, moduleCall := range module.ModuleCalls { + fmt.Fprintf(os.Stderr, "Key: %s\n", key) + body, ok := moduleCall.Config.(*hclsyntax.Body) + if ok { + // We're only interested in getting the `source` attribute, this + // should not have any variables in it. + ctx := renderContext{ + resolve: func(path []string) interface{} { return nil }, + } + properties := ctx.RenderBody(body) + if source, ok := properties["source"]; ok { + if str, ok := source.(string); ok { + fmt.Fprintf(os.Stderr, "Loading submodule: %s\n", str) + childDir := filepath.Join(*dir, str) + + // Construct child path, e.g. `module.child1.aws_vpc.child`. + childPath := make([]string, len(configuration.path)) + copy(childPath, path) + childPath = append(childPath, "module") + childPath = append(childPath, key) + + if child, err := parseDirectory(childPath, childDir); err == nil { + configuration.children[key] = child + } else { + fmt.Fprintf(os.Stderr, "warning: Error loading submodule: %s\n", err) + } + } + } + } + } + } + + configuration.childrenVars = make(map[string]map[string]interface{}) + configuration.vars = make(map[string]interface{}) + + return configuration, nil +} + +// Return a copy of a HclConfiguration with updated variables. +func (c0 *HclConfiguration) withVars(vars map[string]interface{}) *HclConfiguration { + c1 := c0 + c1.vars = vars + return c1 +} + +func (c *HclConfiguration) LoadedFiles() []string { + filepaths := []string{} + if c.dir != nil { + filepaths = append(filepaths, *c.dir) + } + fmt.Fprintf(os.Stderr, "%v\n", c.filepaths) + for _, fp := range c.filepaths { + if c.dir != nil && !filepath.IsAbs(fp) { + fp = filepath.Join(*c.dir, fp) + } + filepaths = append(filepaths, fp) + } + for _, child := range c.children { + if child != nil { + filepaths = append(filepaths, child.LoadedFiles()...) + } + } + return filepaths +} + +func (c *HclConfiguration) Location(attributePath []string) (*Location, error) { + return nil, nil +} + +func (c *HclConfiguration) RegulaInput() RegulaInput { + path := "" + if c.dir != nil { + path = *c.dir + } else { + path = c.filepaths[0] + } + + return RegulaInput{ + "filepath": path, + "content": c.renderResourceView(), + } +} + +func (c *HclConfiguration) renderResourceView() map[string]interface{} { + resourceView := make(map[string]interface{}) + resourceView["hcl_resource_view_version"] = "0.0.1" + resourceView["resources"] = c.renderResources() + return resourceView +} + +func (c *HclConfiguration) qualifiedResourceId(localId string) string { + if len(c.path) == 0 { + return localId + } else { + return strings.Join(c.path, ".") + "." + localId + } +} + +func (c *HclConfiguration) renderResources() map[string]interface{} { + resources := make(map[string]interface{}) + + for resourceId, resource := range c.module.ManagedResources { + resourceId = c.qualifiedResourceId(resourceId) + resources[resourceId] = c.renderResource(resourceId, resource) + } + for resourceId, resource := range c.module.DataResources { + resourceId = c.qualifiedResourceId(resourceId) + resources[resourceId] = c.renderResource(resourceId, resource) + } + + for key, _ := range c.children { + if child, ok := c.GetChild(key); ok { + for resourceId, resource := range child.renderResources() { + resources[resourceId] = resource + } + } + } + + return resources +} + +func (c *HclConfiguration) renderResource( + resourceId string, resource *configs.Resource, +) interface{} { + context := c.renderContext() + context.schema = c.schemas[resource.Type] + + properties := make(map[string]interface{}) + properties["_type"] = resource.Type + properties["id"] = resourceId + + body, ok := resource.Config.(*hclsyntax.Body) + if !ok { + return properties + } + + bodyProperties := context.RenderBody(body) + for k, v := range bodyProperties { + properties[k] = v + } + + // `provider` may be explicitly set. + if provider, ok := properties["provider"]; ok { + properties["_provider"] = provider + delete(properties, "provider") + } else { + properties["_provider"] = resource.Provider.ForDisplay() + } + + return properties +} + +func (c *HclConfiguration) getResource(id string) (*configs.Resource, bool) { + if r, ok := c.module.ManagedResources[id]; ok { + return r, true + } + if r, ok := c.module.DataResources[id]; ok { + return r, true + } + return nil, false +} + +func (c *HclConfiguration) resolveResourceReference(path []string) interface{} { + if len(path) < 1 { + return nil + } + idx := 2 + if path[0] == "data" { + idx = 3 + } + + if len(path) == 2 && path[0] == "var" { + if value, ok := c.vars[path[1]]; ok { + return value + } else { + return nil + } + } + + if len(path) == 3 && path[0] == "module" { + if child, ok := c.GetChild(path[1]); ok { + out := child.GetOutput(path[2]) + fmt.Fprintf(os.Stderr, "%v -> %v\n", path, out) + return out + } + + return nil + } + + if len(path) > 2 && path[0] == "random_string" { + // Random strings are occasionally used to name resource, e.g.: + // "server-${random_string.foo.result}". By not resolving these, + // we get something like "server-random_string.foo.result" which + // usually doesn't conform to naming constraints but it's unique + // enough to make most validations work. + return nil + } + + resourceId := strings.Join(path[:idx], ".") + if resource, ok := c.getResource(resourceId); ok { + resourceNode := TfNode{Object: resource.Config, Range: resource.DeclRange} + if node, err := resourceNode.GetDescendant(path[idx:]); err == nil { + // NOTE: We could handle non-attribute cases but we're usually not + // using these to work with lists and blocks. + if node.Attribute != nil { + expr := node.Attribute.Expr + if e, ok := expr.(hclsyntax.Expression); ok { + ctx := c.renderContext() + return ctx.RenderExpr(e) + } + } + } + + return c.qualifiedResourceId(resourceId) + } + return nil +} + +func (c *HclConfiguration) GetChild(name string) (*HclConfiguration, bool) { + if child, ok := c.children[name]; ok { + childVars, haveChildVars := c.childrenVars[name] + if !haveChildVars { + moduleCall := c.module.ModuleCalls[name] + body, ok := moduleCall.Config.(*hclsyntax.Body) + if ok { + ctx := renderContext{ + resolve: func(path []string) interface{} { return c.resolveResourceReference(path) }, + } + childVars = ctx.RenderBody(body) + for key, val := range childVars { + fmt.Fprintf(os.Stderr, "%s: setting %v to %v\n", name, key, val) + } + } else { + childVars = make(map[string]interface{}) + } + + c.childrenVars[name] = childVars + } + + return child.withVars(childVars), true + } + + return nil, false +} + +func (c *HclConfiguration) GetOutput(name string) interface{} { + if output, ok := c.module.Outputs[name]; ok { + if e, ok := output.Expr.(hclsyntax.Expression); ok { + ctx := c.renderContext() + return ctx.RenderExpr(e) + } + } + + return nil +} + +func (c *HclConfiguration) renderContext() renderContext { + return renderContext{ + resolve: func(path []string) interface{} { return c.resolveResourceReference(path) }, + } +} + +// This is a structure passed down that contains all additional information +// apart from the thing being rendered, which is passed separately. +type renderContext struct { + schema *tf_resource_schemas.Schema + resolve func([]string) interface{} +} + +// Create a copy of the renderContext with a different schema +func (c *renderContext) WithSchema(schema *tf_resource_schemas.Schema) *renderContext { + c1 := c + c1.schema = schema + return c1 +} + +func (c *renderContext) RenderBody(body *hclsyntax.Body) map[string]interface{} { + properties := make(map[string]interface{}) + + for _, attribute := range body.Attributes { + properties[attribute.Name] = c.WithSchema( + tf_resource_schemas.GetAttribute(c.schema, attribute.Name), + ).RenderAttribute(attribute) + } + + tf_resource_schemas.SetDefaultAttributes(c.schema, properties) + + renderedBlocks := make(map[string][]interface{}) + for _, block := range body.Blocks { + if _, ok := renderedBlocks[block.Type]; !ok { + renderedBlocks[block.Type] = make([]interface{}, 0) + } + + childSchema := tf_resource_schemas.GetAttribute(c.schema, block.Type) + if s := tf_resource_schemas.GetElem(childSchema); s != nil { + childSchema = s + } + + entry := c.WithSchema(childSchema).RenderBlock(block) + renderedBlocks[block.Type] = append(renderedBlocks[block.Type], entry) + } + for key, renderedBlock := range renderedBlocks { + properties[key] = renderedBlock + } + + return properties +} + +func (c *renderContext) RenderAttribute(attribute *hclsyntax.Attribute) interface{} { + if attribute.Expr == nil { + return nil + } + return c.RenderExpr(attribute.Expr) +} + +func (c *renderContext) RenderBlock(block *hclsyntax.Block) interface{} { + if block.Body == nil { + return nil + } + return c.RenderBody(block.Body) +} + +// This returns a string or array of references. +func (c *renderContext) ExpressionReferences(expr hclsyntax.Expression) interface{} { + references := make([]interface{}, 0) + for _, traversal := range expr.Variables() { + path := c.RenderTraversal(traversal) + resolved := c.resolve(path) + if resolved != nil { + references = append(references, resolved) + } + } + if len(references) == 0 { + return nil + } else if len(references) == 1 { + return references[0] + } else { + return references + } +} + +// Auxiliary function to determine if the expression should be ignored from +// sets, lists, etc. +func voidExpression(expr hclsyntax.Expression) bool { + switch e := expr.(type) { + case *hclsyntax.TemplateExpr: + return len(e.Parts) == 0 + } + return false +} + +func (c *renderContext) RenderExpr(expr hclsyntax.Expression) interface{} { + switch e := expr.(type) { + case *hclsyntax.TemplateWrapExpr: + return c.RenderExpr(e.Wrapped) + case *hclsyntax.ScopeTraversalExpr: + path := c.RenderTraversal(e.Traversal) + ref := c.resolve(path) + if ref != nil { + return ref + } else { + // Is this useful? This should just map to variables? + return strings.Join(path, ".") + } + case *hclsyntax.TemplateExpr: + if len(e.Parts) == 1 { + return c.RenderExpr(e.Parts[0]) + } + + // This is commonly used to refer to resources, so we pick out the + // references. + refs := c.ExpressionReferences(e) + if refs != nil { + return refs + } + + str := "" + for _, part := range e.Parts { + val := c.RenderExpr(part) + if s, ok := val.(string); ok { + str += s + } + } + return str + case *hclsyntax.LiteralValueExpr: + return c.RenderValue(e.Val) + case *hclsyntax.TupleConsExpr: + arr := make([]interface{}, 0) + ctx := c.WithSchema(tf_resource_schemas.GetElem(c.schema)) + for _, elem := range e.Exprs { + if !voidExpression(elem) { + arr = append(arr, ctx.RenderExpr(elem)) + } + } + return arr + case *hclsyntax.ObjectConsExpr: + object := make(map[string]interface{}) + for _, item := range e.Items { + ctx := c.WithSchema(nil) // Or pass string+elem schema? + key := ctx.RenderExpr(item.KeyExpr) + val := ctx.RenderExpr(item.ValueExpr) + if str, ok := key.(string); ok { + object[str] = val + } else { + fmt.Fprintf(os.Stderr, "warning: non-string key: %s\n", reflect.TypeOf(key).String()) + } + } + return object + case *hclsyntax.ObjectConsKeyExpr: + // Keywords are interpreted as keys. + if key := hcl.ExprAsKeyword(e); key != "" { + return key + } else { + return c.RenderExpr(e.Wrapped) + } + } + + fmt.Fprintf(os.Stderr, "warning: unhandled expression type %s\n", reflect.TypeOf(expr).String()) + + // Fall back to normal eval. + return c.EvaluateExpr(expr) +} + +func (c *renderContext) EvaluateExpr(expr hcl.Expression) interface{} { + ctx := hcl.EvalContext{} + val, _ := expr.Value(&ctx) + return c.RenderValue(val) +} + +func (c *renderContext) RenderTraversal(traversal hcl.Traversal) []string { + parts := make([]string, 0) + + for _, traverser := range traversal { + switch t := traverser.(type) { + case hcl.TraverseRoot: + parts = append(parts, t.Name) + case hcl.TraverseAttr: + parts = append(parts, t.Name) + case hcl.TraverseIndex: + // Should be an integer but treat it a bit more generically. + part := fmt.Sprintf("%v", c.RenderValue(t.Key)) + parts = append(parts, part) + } + } + + return parts +} + +func (c *renderContext) RenderValue(val cty.Value) interface{} { + if val.Type() == cty.Bool { + return val.True() + } else if val.Type() == cty.Number { + b := val.AsBigFloat() + if b.IsInt() { + i, _ := b.Int64() + return i + } else { + f, _ := b.Float64() + return f + } + } else if val.Type() == cty.String { + return val.AsString() + } else if val.Type().IsTupleType() || val.Type().IsSetType() || val.Type().IsListType() { + ctx := c.WithSchema(tf_resource_schemas.GetElem(c.schema)) + array := make([]interface{}, 0) + for _, elem := range val.AsValueSlice() { + array = append(array, ctx.RenderValue(elem)) + } + return array + } else if val.Type().IsMapType() || val.Type().IsObjectType() { + object := make(map[string]interface{}, 0) + for key, attr := range val.AsValueMap() { + ctx := c.WithSchema(tf_resource_schemas.GetAttribute(c.schema, key)) + object[key] = ctx.RenderValue(attr) + } + return object + } + + fmt.Fprintf(os.Stderr, "Unknown type: %v\n", val.Type().GoString()) + return nil +} + +//////////////////////////////////////////////////////////////////////////////// +// utilities for traversing to a path in a HCL tree somewhat generically + +// A `TfNode` represents a syntax tree in the HCL config. +type TfNode struct { + // Exactly one of the next three fields will be set. + Object hcl.Body + Array hcl.Blocks + Attribute *hcl.Attribute + + // This will always be set. + Range hcl.Range +} + +func (node *TfNode) GetChild(key string) (*TfNode, error) { + child := TfNode{} + + if node.Object != nil { + bodyContent, _, diags := node.Object.PartialContent(&hcl.BodySchema{ + Attributes: []hcl.AttributeSchema{ + { + Name: key, + Required: false, + }, + }, + Blocks: []hcl.BlockHeaderSchema{ + { + Type: key, + }, + }, + }) + if diags.HasErrors() { + return nil, fmt.Errorf(diags.Error()) + } + + blocks := bodyContent.Blocks.OfType(key) + if len(blocks) > 0 { + child.Array = blocks + child.Range = blocks[0].DefRange + } + + if attribute, ok := bodyContent.Attributes[key]; ok { + child.Attribute = attribute + child.Range = attribute.Range + } + } else if node.Array != nil { + index, err := strconv.Atoi(key) + if err != nil { + return nil, err + } else { + if index < 0 || index >= len(node.Array) { + return nil, fmt.Errorf("TfNode.Get: out of bounds: %d", index) + } + + child.Object = node.Array[index].Body + child.Range = node.Array[index].DefRange + } + } + + return &child, nil +} + +func (node *TfNode) GetDescendant(path []string) (*TfNode, error) { + if len(path) == 0 { + return node, nil + } + + child, err := node.GetChild(path[0]) + if err != nil { + return nil, err + } + + return child.GetDescendant(path[1:]) +} + +func (node *TfNode) Location() string { + return fmt.Sprintf( + "%s:%d:%d", + node.Range.Filename, + node.Range.Start.Line, + node.Range.Start.Column, + ) +} diff --git a/pkg/tf_resource_schemas/generate/main.go b/pkg/tf_resource_schemas/generate/main.go new file mode 100644 index 00000000..92aa3661 --- /dev/null +++ b/pkg/tf_resource_schemas/generate/main.go @@ -0,0 +1,89 @@ +// Generates a minimal version of terraform's resource schemas that has just +// the info we need and that we can embed easily. Note that these schemas +// focus on how the resource types will be represented in JSON. +package main + +import ( + "encoding/json" + "os" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + provider_aws "github.com/terraform-providers/terraform-provider-aws/aws" + provider_google "github.com/terraform-providers/terraform-provider-google/google" + + "tf_resource_schemas" +) + +func check(e error) { + if e != nil { + panic(e) + } +} + +func ExtractResourceSchema(r *schema.Resource) *tf_resource_schemas.Schema { + objectSchema := new(tf_resource_schemas.Schema) + objectSchema.Attributes = make(map[string]*tf_resource_schemas.Schema) + for key, attr := range r.Schema { + attrSchema := ExtractSchema(attr) + if attrSchema != nil { + objectSchema.Attributes[key] = attrSchema + } + } + + if len(objectSchema.Attributes) > 0 { + return objectSchema + } else { + return nil + } +} + +func ExtractSchema(s *schema.Schema) *tf_resource_schemas.Schema { + switch elem := s.Elem.(type) { + case *schema.Resource: + elemSchema := ExtractResourceSchema(elem) + if elemSchema != nil { + return &tf_resource_schemas.Schema{Elem: elemSchema} + } else { + return nil + } + } + + if s.Default != nil { + return &tf_resource_schemas.Schema{Default: s.Default} + } + + if s.DefaultFunc != nil { + def, err := s.DefaultFunc() + if err == nil { + return &tf_resource_schemas.Schema{Default: def} + } + } + + return nil +} + +func main() { + f, err := os.Create("pkg/tf_resource_schemas/resource_schemas.json") + check(err) + defer f.Close() + + resourceSchemas := make(tf_resource_schemas.ResourceSchemas) + + providers := []*schema.Provider{ + provider_aws.Provider(), + provider_google.Provider(), + } + + for _, provider := range providers { + for resourceType, resource := range provider.ResourcesMap { + resourceSchema := ExtractResourceSchema(resource) + if resourceSchema != nil { + resourceSchemas[resourceType] = resourceSchema + } + } + } + + bytes, err := json.Marshal(resourceSchemas) + check(err) + f.Write(bytes) +} diff --git a/pkg/tf_resource_schemas/go.mod b/pkg/tf_resource_schemas/go.mod new file mode 100644 index 00000000..b48dafc2 --- /dev/null +++ b/pkg/tf_resource_schemas/go.mod @@ -0,0 +1,3 @@ +module main + +go 1.16 diff --git a/pkg/tf_resource_schemas/load.go b/pkg/tf_resource_schemas/load.go new file mode 100644 index 00000000..725af16c --- /dev/null +++ b/pkg/tf_resource_schemas/load.go @@ -0,0 +1,24 @@ +package tf_resource_schemas + +import ( + _ "embed" + "encoding/json" +) + +//go:embed resource_schemas.json +var resourceSchemasJson []byte + +var resourceSchemas ResourceSchemas + +func LoadResourceSchemas() ResourceSchemas { + if resourceSchemas != nil { + return resourceSchemas + } + + resourceSchemas := make(ResourceSchemas) + err := json.Unmarshal(resourceSchemasJson, &resourceSchemas) + if err != nil { + panic(err) + } + return resourceSchemas +} diff --git a/pkg/tf_resource_schemas/resource_schemas.json b/pkg/tf_resource_schemas/resource_schemas.json new file mode 100644 index 00000000..0e624229 --- /dev/null +++ b/pkg/tf_resource_schemas/resource_schemas.json @@ -0,0 +1 @@ +{"aws_accessanalyzer_analyzer":{"attributes":{"type":{"default":"ACCOUNT"}}},"aws_acm_certificate":{"attributes":{"options":{"elem":{"attributes":{"certificate_transparency_logging_preference":{"default":"ENABLED"}}}}}},"aws_acmpca_certificate_authority":{"attributes":{"enabled":{"default":true},"permanent_deletion_time_in_days":{"default":30},"type":{"default":"SUBORDINATE"}}},"aws_alb":{"attributes":{"access_logs":{"elem":{"attributes":{"enabled":{"default":false}}}},"drop_invalid_header_fields":{"default":false},"enable_cross_zone_load_balancing":{"default":false},"enable_deletion_protection":{"default":false},"enable_http2":{"default":true},"idle_timeout":{"default":60},"load_balancer_type":{"default":"application"}}},"aws_alb_listener":{"attributes":{"default_action":{"elem":{"attributes":{"forward":{"elem":{"attributes":{"stickiness":{"elem":{"attributes":{"enabled":{"default":false}}}},"target_group":{"elem":{"attributes":{"weight":{"default":1}}}}}}},"redirect":{"elem":{"attributes":{"host":{"default":"#{host}"},"path":{"default":"/#{path}"},"port":{"default":"#{port}"},"protocol":{"default":"#{protocol}"},"query":{"default":"#{query}"}}}}}}}}},"aws_alb_listener_rule":{"attributes":{"action":{"elem":{"attributes":{"forward":{"elem":{"attributes":{"stickiness":{"elem":{"attributes":{"enabled":{"default":false}}}},"target_group":{"elem":{"attributes":{"weight":{"default":1}}}}}}},"redirect":{"elem":{"attributes":{"host":{"default":"#{host}"},"path":{"default":"/#{path}"},"port":{"default":"#{port}"},"protocol":{"default":"#{protocol}"},"query":{"default":"#{query}"}}}}}}}}},"aws_alb_target_group":{"attributes":{"deregistration_delay":{"default":300},"health_check":{"elem":{"attributes":{"enabled":{"default":true},"healthy_threshold":{"default":3},"interval":{"default":30},"port":{"default":"traffic-port"},"protocol":{"default":"HTTP"},"unhealthy_threshold":{"default":3}}}},"lambda_multi_value_headers_enabled":{"default":false},"proxy_protocol_v2":{"default":false},"slow_start":{"default":0},"stickiness":{"elem":{"attributes":{"cookie_duration":{"default":86400},"enabled":{"default":true}}}},"target_type":{"default":"instance"}}},"aws_ami":{"attributes":{"architecture":{"default":"x86_64"},"ebs_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true},"volume_type":{"default":"standard"}}}},"sriov_net_support":{"default":"simple"},"virtualization_type":{"default":"paravirtual"}}},"aws_ami_copy":{"attributes":{"encrypted":{"default":false}}},"aws_api_gateway_api_key":{"attributes":{"description":{"default":"Managed by Terraform"},"enabled":{"default":true}}},"aws_api_gateway_authorizer":{"attributes":{"authorizer_result_ttl_in_seconds":{"default":300},"identity_source":{"default":"method.request.header.Authorization"},"type":{"default":"TOKEN"}}},"aws_api_gateway_integration":{"attributes":{"connection_type":{"default":"INTERNET"},"timeout_milliseconds":{"default":29000}}},"aws_api_gateway_method":{"attributes":{"api_key_required":{"default":false}}},"aws_api_gateway_method_settings":{"attributes":{"settings":{"elem":{"attributes":{"throttling_burst_limit":{"default":-1},"throttling_rate_limit":{"default":-1}}}}}},"aws_api_gateway_request_validator":{"attributes":{"validate_request_body":{"default":false},"validate_request_parameters":{"default":false}}},"aws_api_gateway_rest_api":{"attributes":{"minimum_compression_size":{"default":-1}}},"aws_api_gateway_usage_plan":{"attributes":{"quota_settings":{"elem":{"attributes":{"offset":{"default":0}}}},"throttle_settings":{"elem":{"attributes":{"burst_limit":{"default":0},"rate_limit":{"default":0}}}}}},"aws_apigatewayv2_api":{"attributes":{"api_key_selection_expression":{"default":"$request.header.x-api-key"},"route_selection_expression":{"default":"$request.method $request.path"}}},"aws_apigatewayv2_integration":{"attributes":{"connection_type":{"default":"INTERNET"},"passthrough_behavior":{"default":"WHEN_NO_MATCH"},"payload_format_version":{"default":"1.0"}}},"aws_apigatewayv2_route":{"attributes":{"api_key_required":{"default":false},"authorization_type":{"default":"NONE"}}},"aws_apigatewayv2_stage":{"attributes":{"auto_deploy":{"default":false},"default_route_settings":{"elem":{"attributes":{"data_trace_enabled":{"default":false},"detailed_metrics_enabled":{"default":false}}}},"route_settings":{"elem":{"attributes":{"data_trace_enabled":{"default":false},"detailed_metrics_enabled":{"default":false}}}}}},"aws_appautoscaling_policy":{"attributes":{"policy_type":{"default":"StepScaling"},"target_tracking_scaling_policy_configuration":{"elem":{"attributes":{"disable_scale_in":{"default":false}}}}}},"aws_appautoscaling_scheduled_action":{"attributes":{"timezone":{"default":"UTC"}}},"aws_appmesh_mesh":{"attributes":{"spec":{"elem":{"attributes":{"egress_filter":{"elem":{"attributes":{"type":{"default":"DROP_ALL"}}}}}}}}},"aws_appmesh_route":{"attributes":{"spec":{"elem":{"attributes":{"grpc_route":{"elem":{"attributes":{"match":{"elem":{"attributes":{"metadata":{"elem":{"attributes":{"invert":{"default":false}}}}}}}}}},"http2_route":{"elem":{"attributes":{"match":{"elem":{"attributes":{"header":{"elem":{"attributes":{"invert":{"default":false}}}}}}}}}},"http_route":{"elem":{"attributes":{"match":{"elem":{"attributes":{"header":{"elem":{"attributes":{"invert":{"default":false}}}}}}}}}}}}}}},"aws_appmesh_virtual_gateway":{"attributes":{"spec":{"elem":{"attributes":{"backend_defaults":{"elem":{"attributes":{"client_policy":{"elem":{"attributes":{"tls":{"elem":{"attributes":{"enforce":{"default":true}}}}}}}}}}}}}}},"aws_appmesh_virtual_node":{"attributes":{"spec":{"elem":{"attributes":{"backend":{"elem":{"attributes":{"virtual_service":{"elem":{"attributes":{"client_policy":{"elem":{"attributes":{"tls":{"elem":{"attributes":{"enforce":{"default":true}}}}}}}}}}}}},"backend_defaults":{"elem":{"attributes":{"client_policy":{"elem":{"attributes":{"tls":{"elem":{"attributes":{"enforce":{"default":true}}}}}}}}}}}}}}},"aws_appsync_api_key":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_appsync_function":{"attributes":{"function_version":{"default":"2018-05-29"}}},"aws_appsync_graphql_api":{"attributes":{"log_config":{"elem":{"attributes":{"exclude_verbose_content":{"default":false}}}}}},"aws_appsync_resolver":{"attributes":{"kind":{"default":"UNIT"}}},"aws_athena_database":{"attributes":{"force_destroy":{"default":false}}},"aws_athena_named_query":{"attributes":{"workgroup":{"default":"primary"}}},"aws_athena_workgroup":{"attributes":{"configuration":{"elem":{"attributes":{"enforce_workgroup_configuration":{"default":true},"publish_cloudwatch_metrics_enabled":{"default":true}}}},"force_destroy":{"default":false},"state":{"default":"ENABLED"}}},"aws_autoscaling_group":{"attributes":{"force_delete":{"default":false},"force_delete_warm_pool":{"default":false},"health_check_grace_period":{"default":300},"instance_refresh":{"elem":{"attributes":{"preferences":{"elem":{"attributes":{"min_healthy_percentage":{"default":90}}}}}}},"metrics_granularity":{"default":"1Minute"},"mixed_instances_policy":{"elem":{"attributes":{"launch_template":{"elem":{"attributes":{"launch_template_specification":{"elem":{"attributes":{"version":{"default":"$Default"}}}},"override":{"elem":{"attributes":{"launch_template_specification":{"elem":{"attributes":{"version":{"default":"$Default"}}}}}}}}}}}}},"protect_from_scale_in":{"default":false},"wait_for_capacity_timeout":{"default":"10m"},"warm_pool":{"elem":{"attributes":{"max_group_prepared_capacity":{"default":-1},"min_size":{"default":0},"pool_state":{"default":"Stopped"}}}}}},"aws_autoscaling_policy":{"attributes":{"policy_type":{"default":"SimpleScaling"},"target_tracking_configuration":{"elem":{"attributes":{"disable_scale_in":{"default":false}}}}}},"aws_autoscalingplans_scaling_plan":{"attributes":{"scaling_instruction":{"elem":{"attributes":{"disable_dynamic_scaling":{"default":false},"scaling_policy_update_behavior":{"default":"KeepExternalPolicies"},"target_tracking_configuration":{"elem":{"attributes":{"disable_scale_in":{"default":false}}}}}}}}},"aws_backup_plan":{"attributes":{"rule":{"elem":{"attributes":{"completion_window":{"default":180},"enable_continuous_backup":{"default":false},"start_window":{"default":60}}}}}},"aws_batch_compute_environment":{"attributes":{"state":{"default":"ENABLED"}}},"aws_budgets_budget":{"attributes":{"cost_types":{"elem":{"attributes":{"include_credit":{"default":true},"include_discount":{"default":true},"include_other_subscription":{"default":true},"include_recurring":{"default":true},"include_refund":{"default":true},"include_subscription":{"default":true},"include_support":{"default":true},"include_tax":{"default":true},"include_upfront":{"default":true},"use_amortized":{"default":false},"use_blended":{"default":false}}}},"time_period_end":{"default":"2087-06-15_00:00"}}},"aws_cloudformation_stack_set":{"attributes":{"permission_model":{"default":"SELF_MANAGED"}}},"aws_cloudformation_stack_set_instance":{"attributes":{"retain_stack":{"default":false}}},"aws_cloudfront_cache_policy":{"attributes":{"default_ttl":{"default":86400},"max_ttl":{"default":31536000},"min_ttl":{"default":0}}},"aws_cloudfront_distribution":{"attributes":{"default_cache_behavior":{"elem":{"attributes":{"compress":{"default":false},"lambda_function_association":{"elem":{"attributes":{"include_body":{"default":false}}}},"min_ttl":{"default":0}}}},"http_version":{"default":"http2"},"is_ipv6_enabled":{"default":false},"logging_config":{"elem":{"attributes":{"include_cookies":{"default":false},"prefix":{"default":""}}}},"ordered_cache_behavior":{"elem":{"attributes":{"compress":{"default":false},"lambda_function_association":{"elem":{"attributes":{"include_body":{"default":false}}}},"min_ttl":{"default":0}}}},"origin":{"elem":{"attributes":{"custom_origin_config":{"elem":{"attributes":{"origin_keepalive_timeout":{"default":5},"origin_read_timeout":{"default":30}}}}}}},"price_class":{"default":"PriceClass_All"},"retain_on_delete":{"default":false},"viewer_certificate":{"elem":{"attributes":{"minimum_protocol_version":{"default":"TLSv1"}}}},"wait_for_deployment":{"default":true}}},"aws_cloudfront_origin_access_identity":{"attributes":{"comment":{"default":""}}},"aws_cloudtrail":{"attributes":{"enable_log_file_validation":{"default":false},"enable_logging":{"default":true},"event_selector":{"elem":{"attributes":{"include_management_events":{"default":true},"read_write_type":{"default":"All"}}}},"include_global_service_events":{"default":true},"is_multi_region_trail":{"default":false},"is_organization_trail":{"default":false}}},"aws_cloudwatch_composite_alarm":{"attributes":{"actions_enabled":{"default":true}}},"aws_cloudwatch_event_permission":{"attributes":{"action":{"default":"events:PutEvents"},"event_bus_name":{"default":"default"}}},"aws_cloudwatch_event_rule":{"attributes":{"event_bus_name":{"default":"default"},"is_enabled":{"default":true}}},"aws_cloudwatch_event_target":{"attributes":{"ecs_target":{"elem":{"attributes":{"launch_type":{"default":"EC2"},"network_configuration":{"elem":{"attributes":{"assign_public_ip":{"default":false}}}},"task_count":{"default":1}}}},"event_bus_name":{"default":"default"}}},"aws_cloudwatch_log_group":{"attributes":{"retention_in_days":{"default":0}}},"aws_cloudwatch_log_subscription_filter":{"attributes":{"distribution":{"default":"ByLogStream"}}},"aws_cloudwatch_metric_alarm":{"attributes":{"actions_enabled":{"default":true},"metric_query":{"elem":{"attributes":{"return_data":{"default":false}}}},"treat_missing_data":{"default":"missing"}}},"aws_codebuild_project":{"attributes":{"artifacts":{"elem":{"attributes":{"encryption_disabled":{"default":false},"override_artifact_name":{"default":false}}}},"badge_enabled":{"default":false},"build_timeout":{"default":"60"},"cache":{"elem":{"attributes":{"type":{"default":"NO_CACHE"}}}},"environment":{"elem":{"attributes":{"environment_variable":{"elem":{"attributes":{"type":{"default":"PLAINTEXT"}}}},"image_pull_credentials_type":{"default":"CODEBUILD"},"privileged_mode":{"default":false}}}},"logs_config":{"elem":{"attributes":{"cloudwatch_logs":{"elem":{"attributes":{"status":{"default":"ENABLED"}}}},"s3_logs":{"elem":{"attributes":{"encryption_disabled":{"default":false},"status":{"default":"DISABLED"}}}}}}},"queued_timeout":{"default":"480"},"secondary_artifacts":{"elem":{"attributes":{"encryption_disabled":{"default":false},"namespace_type":{"default":"NONE"},"override_artifact_name":{"default":false},"packaging":{"default":"NONE"}}}}}},"aws_codebuild_report_group":{"attributes":{"delete_reports":{"default":false},"export_config":{"elem":{"attributes":{"s3_destination":{"elem":{"attributes":{"packaging":{"default":"NONE"}}}}}}}}},"aws_codebuild_webhook":{"attributes":{"filter_group":{"elem":{"attributes":{"filter":{"elem":{"attributes":{"exclude_matched_pattern":{"default":false}}}}}}}}},"aws_codedeploy_app":{"attributes":{"compute_platform":{"default":"Server"}}},"aws_codedeploy_deployment_config":{"attributes":{"compute_platform":{"default":"Server"},"traffic_routing_config":{"elem":{"attributes":{"type":{"default":"AllAtOnce"}}}}}},"aws_codedeploy_deployment_group":{"attributes":{"alarm_configuration":{"elem":{"attributes":{"ignore_poll_alarm_failure":{"default":false}}}},"deployment_config_name":{"default":"CodeDeployDefault.OneAtATime"},"deployment_style":{"elem":{"attributes":{"deployment_option":{"default":"WITHOUT_TRAFFIC_CONTROL"},"deployment_type":{"default":"IN_PLACE"}}}}}},"aws_codestarnotifications_notification_rule":{"attributes":{"status":{"default":"ENABLED"},"target":{"elem":{"attributes":{"type":{"default":"SNS"}}}}}},"aws_cognito_identity_pool":{"attributes":{"allow_unauthenticated_identities":{"default":false},"cognito_identity_providers":{"elem":{"attributes":{"server_side_token_check":{"default":false}}}}}},"aws_cognito_user_pool":{"attributes":{"email_configuration":{"elem":{"attributes":{"email_sending_account":{"default":"COGNITO_DEFAULT"}}}},"mfa_configuration":{"default":"OFF"},"verification_message_template":{"elem":{"attributes":{"default_email_option":{"default":"CONFIRM_WITH_CODE"}}}}}},"aws_cognito_user_pool_client":{"attributes":{"refresh_token_validity":{"default":30},"token_validity_units":{"elem":{"attributes":{"access_token":{"default":"hours"},"id_token":{"default":"hours"},"refresh_token":{"default":"days"}}}}}},"aws_cognito_user_pool_ui_customization":{"attributes":{"client_id":{"default":"ALL"}}},"aws_config_config_rule":{"attributes":{"source":{"elem":{"attributes":{"source_detail":{"elem":{"attributes":{"event_source":{"default":"aws.config"}}}}}}}}},"aws_config_configuration_aggregator":{"attributes":{"account_aggregation_source":{"elem":{"attributes":{"all_regions":{"default":false}}}},"organization_aggregation_source":{"elem":{"attributes":{"all_regions":{"default":false}}}}}},"aws_config_configuration_recorder":{"attributes":{"name":{"default":"default"},"recording_group":{"elem":{"attributes":{"all_supported":{"default":true}}}}}},"aws_config_delivery_channel":{"attributes":{"name":{"default":"default"}}},"aws_cur_report_definition":{"attributes":{"refresh_closed_reports":{"default":true},"report_versioning":{"default":"CREATE_NEW_REPORT"}}},"aws_datasync_location_efs":{"attributes":{"subdirectory":{"default":"/"}}},"aws_datasync_location_smb":{"attributes":{"mount_options":{"elem":{"attributes":{"version":{"default":"AUTOMATIC"}}}}}},"aws_datasync_task":{"attributes":{"options":{"elem":{"attributes":{"atime":{"default":"BEST_EFFORT"},"bytes_per_second":{"default":-1},"gid":{"default":"INT_VALUE"},"mtime":{"default":"PRESERVE"},"posix_permissions":{"default":"PRESERVE"},"preserve_deleted_files":{"default":"PRESERVE"},"preserve_devices":{"default":"NONE"},"uid":{"default":"INT_VALUE"},"verify_mode":{"default":"POINT_IN_TIME_CONSISTENT"}}}}}},"aws_dax_cluster":{"attributes":{"server_side_encryption":{"elem":{"attributes":{"enabled":{"default":false}}}}}},"aws_db_event_subscription":{"attributes":{"enabled":{"default":true}}},"aws_db_instance":{"attributes":{"auto_minor_version_upgrade":{"default":true},"copy_tags_to_snapshot":{"default":false},"delete_automated_backups":{"default":true},"monitoring_interval":{"default":0},"performance_insights_enabled":{"default":false},"publicly_accessible":{"default":false},"skip_final_snapshot":{"default":false}}},"aws_db_option_group":{"attributes":{"option_group_description":{"default":"Managed by Terraform"}}},"aws_db_parameter_group":{"attributes":{"description":{"default":"Managed by Terraform"},"parameter":{"elem":{"attributes":{"apply_method":{"default":"immediate"}}}}}},"aws_db_proxy_default_target_group":{"attributes":{"connection_pool_config":{"elem":{"attributes":{"connection_borrow_timeout":{"default":120},"max_connections_percent":{"default":100},"max_idle_connections_percent":{"default":50}}}}}},"aws_db_security_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_db_subnet_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_default_security_group":{"attributes":{"egress":{"elem":{"attributes":{"self":{"default":false}}}},"ingress":{"elem":{"attributes":{"self":{"default":false}}}},"revoke_rules_on_delete":{"default":false}}},"aws_default_vpc":{"attributes":{"enable_dns_support":{"default":true}}},"aws_directory_service_directory":{"attributes":{"enable_sso":{"default":false},"type":{"default":"SimpleAD"}}},"aws_dlm_lifecycle_policy":{"attributes":{"policy_details":{"elem":{"attributes":{"schedule":{"elem":{"attributes":{"create_rule":{"elem":{"attributes":{"interval_unit":{"default":"HOURS"}}}}}}}}}},"state":{"default":"ENABLED"}}},"aws_dms_endpoint":{"attributes":{"elasticsearch_settings":{"elem":{"attributes":{"error_retry_duration":{"default":300},"full_load_error_percentage":{"default":10}}}},"kafka_settings":{"elem":{"attributes":{"topic":{"default":"kafka-default-topic"}}}},"kinesis_settings":{"elem":{"attributes":{"message_format":{"default":"json"}}}},"mongodb_settings":{"elem":{"attributes":{"auth_mechanism":{"default":"default"},"auth_source":{"default":"admin"},"auth_type":{"default":"password"},"docs_to_investigate":{"default":"1000"},"extract_doc_id":{"default":"false"},"nesting_level":{"default":"none"}}}},"s3_settings":{"elem":{"attributes":{"bucket_folder":{"default":""},"bucket_name":{"default":""},"compression_type":{"default":"NONE"},"csv_delimiter":{"default":","},"csv_row_delimiter":{"default":"\\n"},"date_partition_enabled":{"default":false},"external_table_definition":{"default":""},"service_access_role_arn":{"default":""}}}}}},"aws_dms_event_subscription":{"attributes":{"enabled":{"default":true}}},"aws_docdb_cluster":{"attributes":{"backup_retention_period":{"default":1},"engine":{"default":"docdb"},"port":{"default":27017},"skip_final_snapshot":{"default":false}}},"aws_docdb_cluster_instance":{"attributes":{"auto_minor_version_upgrade":{"default":true},"engine":{"default":"docdb"},"promotion_tier":{"default":0}}},"aws_docdb_cluster_parameter_group":{"attributes":{"description":{"default":"Managed by Terraform"},"parameter":{"elem":{"attributes":{"apply_method":{"default":"pending-reboot"}}}}}},"aws_docdb_subnet_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_dx_hosted_private_virtual_interface":{"attributes":{"mtu":{"default":1500}}},"aws_dx_hosted_transit_virtual_interface":{"attributes":{"mtu":{"default":1500}}},"aws_dx_lag":{"attributes":{"force_destroy":{"default":false}}},"aws_dx_private_virtual_interface":{"attributes":{"mtu":{"default":1500}}},"aws_dx_transit_virtual_interface":{"attributes":{"mtu":{"default":1500}}},"aws_dynamodb_table":{"attributes":{"billing_mode":{"default":"PROVISIONED"},"ttl":{"elem":{"attributes":{"enabled":{"default":false}}}}}},"aws_ebs_encryption_by_default":{"attributes":{"enabled":{"default":true}}},"aws_ec2_capacity_reservation":{"attributes":{"ebs_optimized":{"default":false},"end_date_type":{"default":"unlimited"},"ephemeral_storage":{"default":false},"instance_match_criteria":{"default":"open"},"tenancy":{"default":"default"}}},"aws_ec2_client_vpn_endpoint":{"attributes":{"split_tunnel":{"default":false},"transport_protocol":{"default":"udp"}}},"aws_ec2_fleet":{"attributes":{"excess_capacity_termination_policy":{"default":"termination"},"on_demand_options":{"elem":{"attributes":{"allocation_strategy":{"default":"lowestPrice"}}}},"spot_options":{"elem":{"attributes":{"allocation_strategy":{"default":"lowestPrice"},"instance_interruption_behavior":{"default":"terminate"},"instance_pools_to_use_count":{"default":1}}}},"terminate_instances":{"default":false},"type":{"default":"maintain"}}},"aws_ec2_transit_gateway":{"attributes":{"amazon_side_asn":{"default":64512},"auto_accept_shared_attachments":{"default":"disable"},"default_route_table_association":{"default":"enable"},"default_route_table_propagation":{"default":"enable"},"dns_support":{"default":"enable"},"vpn_ecmp_support":{"default":"enable"}}},"aws_ec2_transit_gateway_prefix_list_reference":{"attributes":{"blackhole":{"default":false}}},"aws_ec2_transit_gateway_route":{"attributes":{"blackhole":{"default":false}}},"aws_ec2_transit_gateway_vpc_attachment":{"attributes":{"appliance_mode_support":{"default":"disable"},"dns_support":{"default":"enable"},"ipv6_support":{"default":"disable"},"transit_gateway_default_route_table_association":{"default":true},"transit_gateway_default_route_table_propagation":{"default":true}}},"aws_ec2_transit_gateway_vpc_attachment_accepter":{"attributes":{"transit_gateway_default_route_table_association":{"default":true},"transit_gateway_default_route_table_propagation":{"default":true}}},"aws_ecr_repository":{"attributes":{"encryption_configuration":{"elem":{"attributes":{"encryption_type":{"default":"AES256"}}}},"image_tag_mutability":{"default":"MUTABLE"}}},"aws_ecrpublic_repository":{"attributes":{"force_destroy":{"default":false}}},"aws_ecs_service":{"attributes":{"deployment_controller":{"elem":{"attributes":{"type":{"default":"ECS"}}}},"deployment_maximum_percent":{"default":200},"deployment_minimum_healthy_percent":{"default":100},"enable_ecs_managed_tags":{"default":false},"enable_execute_command":{"default":false},"network_configuration":{"elem":{"attributes":{"assign_public_ip":{"default":false}}}},"scheduling_strategy":{"default":"REPLICA"},"wait_for_steady_state":{"default":false}}},"aws_ecs_task_definition":{"attributes":{"proxy_configuration":{"elem":{"attributes":{"type":{"default":"APPMESH"}}}},"volume":{"elem":{"attributes":{"docker_volume_configuration":{"elem":{"attributes":{"autoprovision":{"default":false}}}},"efs_volume_configuration":{"elem":{"attributes":{"root_directory":{"default":"/"}}}}}}}}},"aws_efs_file_system":{"attributes":{"throughput_mode":{"default":"bursting"}}},"aws_eks_cluster":{"attributes":{"vpc_config":{"elem":{"attributes":{"endpoint_private_access":{"default":false},"endpoint_public_access":{"default":true}}}}}},"aws_elastic_beanstalk_application_version":{"attributes":{"force_delete":{"default":false}}},"aws_elastic_beanstalk_environment":{"attributes":{"tier":{"default":"WebServer"},"wait_for_ready_timeout":{"default":"20m"}}},"aws_elasticache_parameter_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_elasticache_replication_group":{"attributes":{"auto_minor_version_upgrade":{"default":true},"automatic_failover_enabled":{"default":false},"engine":{"default":"redis"},"multi_az_enabled":{"default":false}}},"aws_elasticache_security_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_elasticache_subnet_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_elasticsearch_domain":{"attributes":{"advanced_security_options":{"elem":{"attributes":{"internal_user_database_enabled":{"default":false}}}},"cluster_config":{"elem":{"attributes":{"dedicated_master_enabled":{"default":false},"instance_count":{"default":1},"instance_type":{"default":"m3.medium.elasticsearch"},"zone_awareness_config":{"elem":{"attributes":{"availability_zone_count":{"default":2}}}}}}},"cognito_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"domain_endpoint_options":{"elem":{"attributes":{"custom_endpoint_enabled":{"default":false},"enforce_https":{"default":true}}}},"elasticsearch_version":{"default":"1.5"},"log_publishing_options":{"elem":{"attributes":{"enabled":{"default":true}}}}}},"aws_elastictranscoder_preset":{"attributes":{"video":{"elem":{"attributes":{"sizing_policy":{"default":"Fit"}}}}}},"aws_elb":{"attributes":{"access_logs":{"elem":{"attributes":{"enabled":{"default":true},"interval":{"default":60}}}},"connection_draining":{"default":false},"connection_draining_timeout":{"default":300},"cross_zone_load_balancing":{"default":true},"idle_timeout":{"default":60}}},"aws_emr_cluster":{"attributes":{"core_instance_fleet":{"elem":{"attributes":{"instance_type_configs":{"elem":{"attributes":{"bid_price_as_percentage_of_on_demand_price":{"default":100},"ebs_config":{"elem":{"attributes":{"volumes_per_instance":{"default":1}}}},"weighted_capacity":{"default":1}}}},"launch_specifications":{"elem":{"attributes":{"spot_specification":{"elem":{"attributes":{"block_duration_minutes":{"default":0}}}}}}},"target_on_demand_capacity":{"default":0},"target_spot_capacity":{"default":0}}}},"core_instance_group":{"elem":{"attributes":{"ebs_config":{"elem":{"attributes":{"volumes_per_instance":{"default":1}}}},"instance_count":{"default":1}}}},"master_instance_fleet":{"elem":{"attributes":{"instance_type_configs":{"elem":{"attributes":{"bid_price_as_percentage_of_on_demand_price":{"default":100},"ebs_config":{"elem":{"attributes":{"volumes_per_instance":{"default":1}}}},"weighted_capacity":{"default":1}}}},"launch_specifications":{"elem":{"attributes":{"spot_specification":{"elem":{"attributes":{"block_duration_minutes":{"default":0}}}}}}},"target_on_demand_capacity":{"default":0},"target_spot_capacity":{"default":0}}}},"master_instance_group":{"elem":{"attributes":{"ebs_config":{"elem":{"attributes":{"volumes_per_instance":{"default":1}}}},"instance_count":{"default":1}}}},"step_concurrency_level":{"default":1},"visible_to_all_users":{"default":true}}},"aws_emr_instance_fleet":{"attributes":{"instance_type_configs":{"elem":{"attributes":{"bid_price_as_percentage_of_on_demand_price":{"default":100},"ebs_config":{"elem":{"attributes":{"volumes_per_instance":{"default":1}}}},"weighted_capacity":{"default":1}}}},"launch_specifications":{"elem":{"attributes":{"spot_specification":{"elem":{"attributes":{"block_duration_minutes":{"default":0}}}}}}},"target_on_demand_capacity":{"default":0},"target_spot_capacity":{"default":0}}},"aws_emr_instance_group":{"attributes":{"ebs_config":{"elem":{"attributes":{"volumes_per_instance":{"default":1}}}},"instance_count":{"default":1}}},"aws_flow_log":{"attributes":{"log_destination_type":{"default":"cloud-watch-logs"},"max_aggregation_interval":{"default":600}}},"aws_fms_policy":{"attributes":{"delete_all_policy_resources":{"default":true}}},"aws_fsx_lustre_file_system":{"attributes":{"copy_tags_to_backups":{"default":false},"deployment_type":{"default":"SCRATCH_1"},"storage_type":{"default":"SSD"}}},"aws_fsx_windows_file_system":{"attributes":{"automatic_backup_retention_days":{"default":7},"copy_tags_to_backups":{"default":false},"deployment_type":{"default":"SINGLE_AZ_1"},"self_managed_active_directory":{"elem":{"attributes":{"file_system_administrators_group":{"default":"Domain Admins"}}}},"skip_final_backup":{"default":false},"storage_type":{"default":"SSD"}}},"aws_gamelift_fleet":{"attributes":{"fleet_type":{"default":"ON_DEMAND"},"new_game_session_protection_policy":{"default":"NoProtection"}}},"aws_glacier_vault_lock":{"attributes":{"ignore_deletion_error":{"default":false}}},"aws_globalaccelerator_accelerator":{"attributes":{"attributes":{"elem":{"attributes":{"flow_logs_enabled":{"default":false}}}},"enabled":{"default":true},"ip_address_type":{"default":"IPV4"}}},"aws_globalaccelerator_endpoint_group":{"attributes":{"health_check_interval_seconds":{"default":30},"health_check_protocol":{"default":"TCP"},"threshold_count":{"default":3},"traffic_dial_percentage":{"default":100}}},"aws_globalaccelerator_listener":{"attributes":{"client_affinity":{"default":"NONE"}}},"aws_glue_classifier":{"attributes":{"csv_classifier":{"elem":{"attributes":{"disable_value_trimming":{"default":true}}}}}},"aws_glue_connection":{"attributes":{"connection_type":{"default":"JDBC"}}},"aws_glue_crawler":{"attributes":{"dynamodb_target":{"elem":{"attributes":{"scan_all":{"default":true}}}},"lineage_configuration":{"elem":{"attributes":{"crawler_lineage_settings":{"default":"DISABLE"}}}},"mongodb_target":{"elem":{"attributes":{"scan_all":{"default":true}}}},"recrawl_policy":{"elem":{"attributes":{"recrawl_behavior":{"default":"CRAWL_EVERYTHING"}}}},"schema_change_policy":{"elem":{"attributes":{"delete_behavior":{"default":"DEPRECATE_IN_DATABASE"},"update_behavior":{"default":"UPDATE_IN_DATABASE"}}}}}},"aws_glue_job":{"attributes":{"command":{"elem":{"attributes":{"name":{"default":"glueetl"}}}},"execution_property":{"elem":{"attributes":{"max_concurrent_runs":{"default":1}}}},"timeout":{"default":2880}}},"aws_glue_ml_transform":{"attributes":{"timeout":{"default":2880}}},"aws_glue_security_configuration":{"attributes":{"encryption_configuration":{"elem":{"attributes":{"cloudwatch_encryption":{"elem":{"attributes":{"cloudwatch_encryption_mode":{"default":"DISABLED"}}}},"job_bookmarks_encryption":{"elem":{"attributes":{"job_bookmarks_encryption_mode":{"default":"DISABLED"}}}},"s3_encryption":{"elem":{"attributes":{"s3_encryption_mode":{"default":"DISABLED"}}}}}}}}},"aws_glue_trigger":{"attributes":{"enabled":{"default":true},"predicate":{"elem":{"attributes":{"conditions":{"elem":{"attributes":{"logical_operator":{"default":"EQUALS"}}}},"logical":{"default":"AND"}}}}}},"aws_guardduty_detector":{"attributes":{"enable":{"default":true}}},"aws_guardduty_publishing_destination":{"attributes":{"destination_type":{"default":"S3"}}},"aws_iam_account_password_policy":{"attributes":{"allow_users_to_change_password":{"default":true},"minimum_password_length":{"default":6}}},"aws_iam_group":{"attributes":{"path":{"default":"/"}}},"aws_iam_instance_profile":{"attributes":{"path":{"default":"/"}}},"aws_iam_policy":{"attributes":{"path":{"default":"/"}}},"aws_iam_role":{"attributes":{"force_detach_policies":{"default":false},"max_session_duration":{"default":3600},"path":{"default":"/"}}},"aws_iam_server_certificate":{"attributes":{"path":{"default":"/"}}},"aws_iam_user":{"attributes":{"force_destroy":{"default":false},"path":{"default":"/"}}},"aws_iam_user_login_profile":{"attributes":{"password_length":{"default":20},"password_reset_required":{"default":true}}},"aws_imagebuilder_image":{"attributes":{"enhanced_image_metadata_enabled":{"default":true},"image_tests_configuration":{"elem":{"attributes":{"image_tests_enabled":{"default":true},"timeout_minutes":{"default":720}}}}}},"aws_imagebuilder_image_pipeline":{"attributes":{"enhanced_image_metadata_enabled":{"default":true},"image_tests_configuration":{"elem":{"attributes":{"image_tests_enabled":{"default":true},"timeout_minutes":{"default":720}}}},"schedule":{"elem":{"attributes":{"pipeline_execution_start_condition":{"default":"EXPRESSION_MATCH_AND_DEPENDENCY_UPDATES_AVAILABLE"}}}},"status":{"default":"ENABLED"}}},"aws_imagebuilder_infrastructure_configuration":{"attributes":{"logging":{"elem":{"attributes":{"s3_logs":{"elem":{"attributes":{"s3_key_prefix":{"default":"/"}}}}}}},"terminate_instance_on_failure":{"default":false}}},"aws_instance":{"attributes":{"ebs_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}},"get_password_data":{"default":false},"network_interface":{"elem":{"attributes":{"delete_on_termination":{"default":false}}}},"root_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}},"source_dest_check":{"default":true}}},"aws_iot_role_alias":{"attributes":{"credential_duration":{"default":3600}}},"aws_iot_thing_type":{"attributes":{"deprecated":{"default":false}}},"aws_iot_topic_rule":{"attributes":{"error_action":{"elem":{"attributes":{"republish":{"elem":{"attributes":{"qos":{"default":0}}}},"sns":{"elem":{"attributes":{"message_format":{"default":"RAW"}}}}}}},"republish":{"elem":{"attributes":{"qos":{"default":0}}}},"sns":{"elem":{"attributes":{"message_format":{"default":"RAW"}}}}}},"aws_kinesis_firehose_delivery_stream":{"attributes":{"elasticsearch_configuration":{"elem":{"attributes":{"buffering_interval":{"default":300},"buffering_size":{"default":5},"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"index_rotation_period":{"default":"OneDay"},"retry_duration":{"default":300},"s3_backup_mode":{"default":"FailedDocumentsOnly"}}}},"extended_s3_configuration":{"elem":{"attributes":{"buffer_interval":{"default":300},"buffer_size":{"default":5},"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"compression_format":{"default":"UNCOMPRESSED"},"data_format_conversion_configuration":{"elem":{"attributes":{"enabled":{"default":true},"input_format_configuration":{"elem":{"attributes":{"deserializer":{"elem":{"attributes":{"open_x_json_ser_de":{"elem":{"attributes":{"case_insensitive":{"default":true},"convert_dots_in_json_keys_to_underscores":{"default":false}}}}}}}}}},"output_format_configuration":{"elem":{"attributes":{"serializer":{"elem":{"attributes":{"orc_ser_de":{"elem":{"attributes":{"block_size_bytes":{"default":268435456},"bloom_filter_false_positive_probability":{"default":0.05},"compression":{"default":"SNAPPY"},"dictionary_key_threshold":{"default":0},"enable_padding":{"default":false},"format_version":{"default":"V0_12"},"padding_tolerance":{"default":0.05},"row_index_stride":{"default":10000},"stripe_size_bytes":{"default":67108864}}}},"parquet_ser_de":{"elem":{"attributes":{"block_size_bytes":{"default":268435456},"compression":{"default":"SNAPPY"},"enable_dictionary_compression":{"default":false},"max_padding_bytes":{"default":0},"page_size_bytes":{"default":1048576},"writer_version":{"default":"V1"}}}}}}}}}},"schema_configuration":{"elem":{"attributes":{"version_id":{"default":"LATEST"}}}}}}},"s3_backup_configuration":{"elem":{"attributes":{"buffer_interval":{"default":300},"buffer_size":{"default":5},"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"compression_format":{"default":"UNCOMPRESSED"}}}},"s3_backup_mode":{"default":"Disabled"}}}},"http_endpoint_configuration":{"elem":{"attributes":{"buffering_interval":{"default":300},"buffering_size":{"default":5},"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"request_configuration":{"elem":{"attributes":{"content_encoding":{"default":"NONE"}}}},"retry_duration":{"default":300},"s3_backup_mode":{"default":"FailedDataOnly"}}}},"redshift_configuration":{"elem":{"attributes":{"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"retry_duration":{"default":3600},"s3_backup_configuration":{"elem":{"attributes":{"buffer_interval":{"default":300},"buffer_size":{"default":5},"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"compression_format":{"default":"UNCOMPRESSED"}}}},"s3_backup_mode":{"default":"Disabled"}}}},"s3_configuration":{"elem":{"attributes":{"buffer_interval":{"default":300},"buffer_size":{"default":5},"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"compression_format":{"default":"UNCOMPRESSED"}}}},"server_side_encryption":{"elem":{"attributes":{"enabled":{"default":false},"key_type":{"default":"AWS_OWNED_CMK"}}}},"splunk_configuration":{"elem":{"attributes":{"cloudwatch_logging_options":{"elem":{"attributes":{"enabled":{"default":false}}}},"hec_acknowledgment_timeout":{"default":180},"hec_endpoint_type":{"default":"Raw"},"retry_duration":{"default":3600},"s3_backup_mode":{"default":"FailedEventsOnly"}}}}}},"aws_kinesis_stream":{"attributes":{"encryption_type":{"default":"NONE"},"enforce_consumer_deletion":{"default":false},"retention_period":{"default":24}}},"aws_kinesis_video_stream":{"attributes":{"data_retention_in_hours":{"default":0}}},"aws_kms_external_key":{"attributes":{"deletion_window_in_days":{"default":30}}},"aws_kms_grant":{"attributes":{"retire_on_delete":{"default":false}}},"aws_kms_key":{"attributes":{"customer_master_key_spec":{"default":"SYMMETRIC_DEFAULT"},"enable_key_rotation":{"default":false},"is_enabled":{"default":true},"key_usage":{"default":"ENCRYPT_DECRYPT"}}},"aws_lakeformation_permissions":{"attributes":{"catalog_resource":{"default":false},"table":{"elem":{"attributes":{"wildcard":{"default":false}}}}}},"aws_lambda_event_source_mapping":{"attributes":{"enabled":{"default":true}}},"aws_lambda_function":{"attributes":{"memory_size":{"default":128},"package_type":{"default":"Zip"},"publish":{"default":false},"reserved_concurrent_executions":{"default":-1},"timeout":{"default":3}}},"aws_lambda_function_event_invoke_config":{"attributes":{"maximum_retry_attempts":{"default":2}}},"aws_launch_configuration":{"attributes":{"associate_public_ip_address":{"default":false},"ebs_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}},"enable_monitoring":{"default":true},"root_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}}}},"aws_lb":{"attributes":{"access_logs":{"elem":{"attributes":{"enabled":{"default":false}}}},"drop_invalid_header_fields":{"default":false},"enable_cross_zone_load_balancing":{"default":false},"enable_deletion_protection":{"default":false},"enable_http2":{"default":true},"idle_timeout":{"default":60},"load_balancer_type":{"default":"application"}}},"aws_lb_listener":{"attributes":{"default_action":{"elem":{"attributes":{"forward":{"elem":{"attributes":{"stickiness":{"elem":{"attributes":{"enabled":{"default":false}}}},"target_group":{"elem":{"attributes":{"weight":{"default":1}}}}}}},"redirect":{"elem":{"attributes":{"host":{"default":"#{host}"},"path":{"default":"/#{path}"},"port":{"default":"#{port}"},"protocol":{"default":"#{protocol}"},"query":{"default":"#{query}"}}}}}}}}},"aws_lb_listener_rule":{"attributes":{"action":{"elem":{"attributes":{"forward":{"elem":{"attributes":{"stickiness":{"elem":{"attributes":{"enabled":{"default":false}}}},"target_group":{"elem":{"attributes":{"weight":{"default":1}}}}}}},"redirect":{"elem":{"attributes":{"host":{"default":"#{host}"},"path":{"default":"/#{path}"},"port":{"default":"#{port}"},"protocol":{"default":"#{protocol}"},"query":{"default":"#{query}"}}}}}}}}},"aws_lb_target_group":{"attributes":{"deregistration_delay":{"default":300},"health_check":{"elem":{"attributes":{"enabled":{"default":true},"healthy_threshold":{"default":3},"interval":{"default":30},"port":{"default":"traffic-port"},"protocol":{"default":"HTTP"},"unhealthy_threshold":{"default":3}}}},"lambda_multi_value_headers_enabled":{"default":false},"proxy_protocol_v2":{"default":false},"slow_start":{"default":0},"stickiness":{"elem":{"attributes":{"cookie_duration":{"default":86400},"enabled":{"default":true}}}},"target_type":{"default":"instance"}}},"aws_lex_bot":{"attributes":{"create_version":{"default":false},"detect_sentiment":{"default":false},"enable_model_improvements":{"default":false},"idle_session_ttl_in_seconds":{"default":300},"locale":{"default":"en-US"},"nlu_intent_confidence_threshold":{"default":0},"process_behavior":{"default":"SAVE"}}},"aws_lex_bot_alias":{"attributes":{"description":{"default":""}}},"aws_lex_intent":{"attributes":{"create_version":{"default":false},"slot":{"elem":{"attributes":{"description":{"default":""},"priority":{"default":0}}}}}},"aws_lex_slot_type":{"attributes":{"create_version":{"default":false},"description":{"default":""},"value_selection_strategy":{"default":"ORIGINAL_VALUE"}}},"aws_licensemanager_license_configuration":{"attributes":{"license_count_hard_limit":{"default":false}}},"aws_macie_s3_bucket_association":{"attributes":{"classification_type":{"elem":{"attributes":{"continuous":{"default":"FULL"},"one_time":{"default":"NONE"}}}}}},"aws_media_convert_queue":{"attributes":{"pricing_plan":{"default":"ON_DEMAND"},"status":{"default":"ACTIVE"}}},"aws_media_package_channel":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_mq_broker":{"attributes":{"apply_immediately":{"default":false},"auto_minor_version_upgrade":{"default":false},"deployment_mode":{"default":"SINGLE_INSTANCE"},"encryption_options":{"elem":{"attributes":{"use_aws_owned_key":{"default":true}}}},"publicly_accessible":{"default":false},"user":{"elem":{"attributes":{"console_access":{"default":false}}}}}},"aws_msk_cluster":{"attributes":{"broker_node_group_info":{"elem":{"attributes":{"az_distribution":{"default":"DEFAULT"}}}},"encryption_info":{"elem":{"attributes":{"encryption_in_transit":{"elem":{"attributes":{"client_broker":{"default":"TLS"},"in_cluster":{"default":true}}}}}}},"enhanced_monitoring":{"default":"DEFAULT"}}},"aws_neptune_cluster":{"attributes":{"backup_retention_period":{"default":1},"engine":{"default":"neptune"},"neptune_cluster_parameter_group_name":{"default":"default.neptune1"},"port":{"default":8182},"skip_final_snapshot":{"default":false},"storage_encrypted":{"default":false}}},"aws_neptune_cluster_instance":{"attributes":{"auto_minor_version_upgrade":{"default":true},"engine":{"default":"neptune"},"neptune_parameter_group_name":{"default":"default.neptune1"},"port":{"default":8182},"promotion_tier":{"default":0},"publicly_accessible":{"default":false}}},"aws_neptune_cluster_parameter_group":{"attributes":{"description":{"default":"Managed by Terraform"},"parameter":{"elem":{"attributes":{"apply_method":{"default":"pending-reboot"}}}}}},"aws_neptune_event_subscription":{"attributes":{"enabled":{"default":true}}},"aws_neptune_parameter_group":{"attributes":{"description":{"default":"Managed by Terraform"},"parameter":{"elem":{"attributes":{"apply_method":{"default":"pending-reboot"}}}}}},"aws_neptune_subnet_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_network_acl_rule":{"attributes":{"egress":{"default":false}}},"aws_network_interface":{"attributes":{"source_dest_check":{"default":true}}},"aws_networkfirewall_firewall":{"attributes":{"delete_protection":{"default":false}}},"aws_opsworks_application":{"attributes":{"enable_ssl":{"default":false},"environment":{"elem":{"attributes":{"secure":{"default":true}}}}}},"aws_opsworks_custom_layer":{"attributes":{"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_ganglia_layer":{"attributes":{"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"name":{"default":"Ganglia"},"url":{"default":"/ganglia"},"use_ebs_optimized_instances":{"default":false},"username":{"default":"opsworks"}}},"aws_opsworks_haproxy_layer":{"attributes":{"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"healthcheck_method":{"default":"OPTIONS"},"healthcheck_url":{"default":"/"},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"name":{"default":"HAProxy"},"stats_enabled":{"default":true},"stats_url":{"default":"/haproxy?stats"},"stats_user":{"default":"opsworks"},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_instance":{"attributes":{"agent_version":{"default":"INHERIT"},"architecture":{"default":"x86_64"},"delete_ebs":{"default":true},"delete_eip":{"default":true},"ebs_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}},"ebs_optimized":{"default":false},"install_updates_on_boot":{"default":true},"root_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}}}},"aws_opsworks_java_app_layer":{"attributes":{"app_server":{"default":"tomcat"},"app_server_version":{"default":"7"},"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"jvm_options":{"default":""},"jvm_type":{"default":"openjdk"},"jvm_version":{"default":"7"},"name":{"default":"Java App Server"},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_memcached_layer":{"attributes":{"allocated_memory":{"default":512},"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"name":{"default":"Memcached"},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_mysql_layer":{"attributes":{"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"name":{"default":"MySQL"},"root_password_on_all_instances":{"default":true},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_nodejs_app_layer":{"attributes":{"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"name":{"default":"Node.js App Server"},"nodejs_version":{"default":"0.10.38"},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_php_app_layer":{"attributes":{"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"name":{"default":"PHP App Server"},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_rails_app_layer":{"attributes":{"app_server":{"default":"apache_passenger"},"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"bundler_version":{"default":"1.5.3"},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"manage_bundler":{"default":true},"name":{"default":"Rails App Server"},"passenger_version":{"default":"4.0.46"},"ruby_version":{"default":"2.0.0"},"rubygems_version":{"default":"2.2.2"},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_stack":{"attributes":{"berkshelf_version":{"default":"3.2.0"},"configuration_manager_name":{"default":"Chef"},"configuration_manager_version":{"default":"11.10"},"default_os":{"default":"Ubuntu 12.04 LTS"},"default_root_device_type":{"default":"instance-store"},"hostname_theme":{"default":"Layer_Dependent"},"manage_berkshelf":{"default":false},"use_custom_cookbooks":{"default":false},"use_opsworks_security_groups":{"default":true}}},"aws_opsworks_static_web_layer":{"attributes":{"auto_assign_elastic_ips":{"default":false},"auto_assign_public_ips":{"default":false},"auto_healing":{"default":true},"drain_elb_on_shutdown":{"default":true},"ebs_volume":{"elem":{"attributes":{"encrypted":{"default":false},"iops":{"default":0},"raid_level":{"default":""},"type":{"default":"standard"}}}},"install_updates_on_boot":{"default":true},"instance_shutdown_timeout":{"default":120},"name":{"default":"Static Web Server"},"use_ebs_optimized_instances":{"default":false}}},"aws_opsworks_user_profile":{"attributes":{"allow_self_management":{"default":false}}},"aws_organizations_organization":{"attributes":{"feature_set":{"default":"ALL"}}},"aws_organizations_policy":{"attributes":{"type":{"default":"SERVICE_CONTROL_POLICY"}}},"aws_pinpoint_adm_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_apns_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_apns_sandbox_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_apns_voip_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_apns_voip_sandbox_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_baidu_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_email_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_gcm_channel":{"attributes":{"enabled":{"default":true}}},"aws_pinpoint_sms_channel":{"attributes":{"enabled":{"default":true}}},"aws_qldb_ledger":{"attributes":{"deletion_protection":{"default":true}}},"aws_quicksight_group":{"attributes":{"namespace":{"default":"default"}}},"aws_quicksight_user":{"attributes":{"namespace":{"default":"default"}}},"aws_ram_resource_share":{"attributes":{"allow_external_principals":{"default":false}}},"aws_rds_cluster":{"attributes":{"backup_retention_period":{"default":1},"copy_tags_to_snapshot":{"default":false},"enable_http_endpoint":{"default":false},"engine":{"default":"aurora"},"engine_mode":{"default":"provisioned"},"scaling_configuration":{"elem":{"attributes":{"auto_pause":{"default":true},"max_capacity":{"default":16},"min_capacity":{"default":1},"seconds_until_auto_pause":{"default":300},"timeout_action":{"default":"RollbackCapacityChange"}}}},"skip_final_snapshot":{"default":false}}},"aws_rds_cluster_instance":{"attributes":{"auto_minor_version_upgrade":{"default":true},"copy_tags_to_snapshot":{"default":false},"engine":{"default":"aurora"},"monitoring_interval":{"default":0},"promotion_tier":{"default":0},"publicly_accessible":{"default":false}}},"aws_rds_cluster_parameter_group":{"attributes":{"description":{"default":"Managed by Terraform"},"parameter":{"elem":{"attributes":{"apply_method":{"default":"immediate"}}}}}},"aws_rds_global_cluster":{"attributes":{"deletion_protection":{"default":false}}},"aws_redshift_cluster":{"attributes":{"allow_version_upgrade":{"default":true},"automated_snapshot_retention_period":{"default":1},"cluster_version":{"default":"1.0"},"encrypted":{"default":false},"number_of_nodes":{"default":1},"port":{"default":5439},"publicly_accessible":{"default":true},"skip_final_snapshot":{"default":false},"snapshot_copy":{"elem":{"attributes":{"retention_period":{"default":7}}}}}},"aws_redshift_event_subscription":{"attributes":{"enabled":{"default":true}}},"aws_redshift_parameter_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_redshift_security_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_redshift_snapshot_schedule":{"attributes":{"force_destroy":{"default":false}}},"aws_redshift_subnet_group":{"attributes":{"description":{"default":"Managed by Terraform"}}},"aws_resourcegroups_group":{"attributes":{"resource_query":{"elem":{"attributes":{"type":{"default":"TAG_FILTERS_1_0"}}}}}},"aws_route53_health_check":{"attributes":{"disabled":{"default":false},"measure_latency":{"default":false}}},"aws_route53_hosted_zone_dnssec":{"attributes":{"signing_status":{"default":"SIGNING"}}},"aws_route53_key_signing_key":{"attributes":{"status":{"default":"ACTIVE"}}},"aws_route53_resolver_rule":{"attributes":{"target_ip":{"elem":{"attributes":{"port":{"default":53}}}}}},"aws_route53_zone":{"attributes":{"comment":{"default":"Managed by Terraform"},"force_destroy":{"default":false}}},"aws_s3_access_point":{"attributes":{"public_access_block_configuration":{"elem":{"attributes":{"block_public_acls":{"default":true},"block_public_policy":{"default":true},"ignore_public_acls":{"default":true},"restrict_public_buckets":{"default":true}}}}}},"aws_s3_account_public_access_block":{"attributes":{"block_public_acls":{"default":false},"block_public_policy":{"default":false},"ignore_public_acls":{"default":false},"restrict_public_buckets":{"default":false}}},"aws_s3_bucket":{"attributes":{"acl":{"default":"private"},"force_destroy":{"default":false},"versioning":{"elem":{"attributes":{"enabled":{"default":false},"mfa_delete":{"default":false}}}}}},"aws_s3_bucket_analytics_configuration":{"attributes":{"storage_class_analysis":{"elem":{"attributes":{"data_export":{"elem":{"attributes":{"destination":{"elem":{"attributes":{"s3_bucket_destination":{"elem":{"attributes":{"format":{"default":"CSV"}}}}}}},"output_schema_version":{"default":"V_1"}}}}}}}}},"aws_s3_bucket_inventory":{"attributes":{"enabled":{"default":true}}},"aws_s3_bucket_object":{"attributes":{"acl":{"default":"private"},"force_destroy":{"default":false}}},"aws_s3_bucket_public_access_block":{"attributes":{"block_public_acls":{"default":false},"block_public_policy":{"default":false},"ignore_public_acls":{"default":false},"restrict_public_buckets":{"default":false}}},"aws_s3_object_copy":{"attributes":{"acl":{"default":"private"},"force_destroy":{"default":false}}},"aws_s3control_bucket_lifecycle_configuration":{"attributes":{"rule":{"elem":{"attributes":{"expiration":{"elem":{"attributes":{"expired_object_delete_marker":{"default":false}}}},"status":{"default":"Enabled"}}}}}},"aws_sagemaker_app_image_config":{"attributes":{"kernel_gateway_image_config":{"elem":{"attributes":{"file_system_config":{"elem":{"attributes":{"default_gid":{"default":100},"default_uid":{"default":1000},"mount_path":{"default":"/home/sagemaker-user"}}}}}}}}},"aws_sagemaker_domain":{"attributes":{"app_network_access_type":{"default":"PublicInternetOnly"},"default_user_settings":{"elem":{"attributes":{"sharing_settings":{"elem":{"attributes":{"notebook_output_option":{"default":"Disabled"}}}}}}}}},"aws_sagemaker_endpoint_configuration":{"attributes":{"production_variants":{"elem":{"attributes":{"initial_variant_weight":{"default":1}}}}}},"aws_sagemaker_feature_group":{"attributes":{"online_store_config":{"elem":{"attributes":{"enable_online_store":{"default":false}}}}}},"aws_sagemaker_model":{"attributes":{"container":{"elem":{"attributes":{"mode":{"default":"SingleModel"}}}},"primary_container":{"elem":{"attributes":{"mode":{"default":"SingleModel"}}}}}},"aws_sagemaker_notebook_instance":{"attributes":{"direct_internet_access":{"default":"Enabled"},"root_access":{"default":"Enabled"},"volume_size":{"default":5}}},"aws_sagemaker_user_profile":{"attributes":{"user_settings":{"elem":{"attributes":{"sharing_settings":{"elem":{"attributes":{"notebook_output_option":{"default":"Disabled"}}}}}}}}},"aws_secretsmanager_secret":{"attributes":{"recovery_window_in_days":{"default":30}}},"aws_security_group":{"attributes":{"description":{"default":"Managed by Terraform"},"egress":{"elem":{"attributes":{"self":{"default":false}}}},"ingress":{"elem":{"attributes":{"self":{"default":false}}}},"revoke_rules_on_delete":{"default":false}}},"aws_security_group_rule":{"attributes":{"self":{"default":false}}},"aws_service_discovery_service":{"attributes":{"dns_config":{"elem":{"attributes":{"routing_policy":{"default":"MULTIVALUE"}}}}}},"aws_ses_configuration_set":{"attributes":{"delivery_options":{"elem":{"attributes":{"tls_policy":{"default":"Optional"}}}}}},"aws_ses_domain_mail_from":{"attributes":{"behavior_on_mx_failure":{"default":"UseDefaultValue"}}},"aws_ses_event_destination":{"attributes":{"enabled":{"default":false}}},"aws_ses_receipt_rule":{"attributes":{"enabled":{"default":false},"scan_enabled":{"default":false}}},"aws_sfn_state_machine":{"attributes":{"type":{"default":"STANDARD"}}},"aws_signer_signing_job":{"attributes":{"ignore_signing_job_failure":{"default":false}}},"aws_sns_topic":{"attributes":{"content_based_deduplication":{"default":false},"fifo_topic":{"default":false}}},"aws_sns_topic_subscription":{"attributes":{"confirmation_timeout_in_minutes":{"default":1},"endpoint_auto_confirms":{"default":false},"raw_message_delivery":{"default":false}}},"aws_spot_fleet_request":{"attributes":{"allocation_strategy":{"default":"lowestPrice"},"excess_capacity_termination_policy":{"default":"Default"},"fleet_type":{"default":"maintain"},"instance_interruption_behaviour":{"default":"terminate"},"instance_pools_to_use_count":{"default":1},"launch_specification":{"elem":{"attributes":{"associate_public_ip_address":{"default":false},"ebs_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}},"ebs_optimized":{"default":false},"monitoring":{"default":false},"root_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}}}}},"replace_unhealthy_instances":{"default":false},"wait_for_fulfillment":{"default":false}}},"aws_spot_instance_request":{"attributes":{"ebs_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}},"get_password_data":{"default":false},"instance_interruption_behaviour":{"default":"terminate"},"network_interface":{"elem":{"attributes":{"delete_on_termination":{"default":false}}}},"root_block_device":{"elem":{"attributes":{"delete_on_termination":{"default":true}}}},"source_dest_check":{"default":true},"spot_type":{"default":"persistent"},"wait_for_fulfillment":{"default":false}}},"aws_sqs_queue":{"attributes":{"content_based_deduplication":{"default":false},"delay_seconds":{"default":0},"fifo_queue":{"default":false},"max_message_size":{"default":262144},"message_retention_seconds":{"default":345600},"receive_wait_time_seconds":{"default":0},"visibility_timeout_seconds":{"default":30}}},"aws_ssm_association":{"attributes":{"apply_only_at_cron_interval":{"default":false}}},"aws_ssm_document":{"attributes":{"document_format":{"default":"JSON"}}},"aws_ssm_maintenance_window":{"attributes":{"allow_unassociated_targets":{"default":false},"enabled":{"default":true}}},"aws_ssm_parameter":{"attributes":{"tier":{"default":"Standard"}}},"aws_ssm_patch_baseline":{"attributes":{"approval_rule":{"elem":{"attributes":{"compliance_level":{"default":"UNSPECIFIED"},"enable_non_security":{"default":false}}}},"approved_patches_compliance_level":{"default":"UNSPECIFIED"},"operating_system":{"default":"WINDOWS"}}},"aws_ssm_resource_data_sync":{"attributes":{"s3_destination":{"elem":{"attributes":{"sync_format":{"default":"JsonSerDe"}}}}}},"aws_ssoadmin_permission_set":{"attributes":{"session_duration":{"default":"PT1H"}}},"aws_storagegateway_gateway":{"attributes":{"gateway_type":{"default":"STORED"},"smb_active_directory_settings":{"elem":{"attributes":{"timeout_in_seconds":{"default":20}}}}}},"aws_storagegateway_nfs_file_share":{"attributes":{"default_storage_class":{"default":"S3_STANDARD"},"guess_mime_type_enabled":{"default":true},"kms_encrypted":{"default":false},"nfs_file_share_defaults":{"elem":{"attributes":{"directory_mode":{"default":"0777"},"file_mode":{"default":"0666"},"group_id":{"default":"65534"},"owner_id":{"default":"65534"}}}},"notification_policy":{"default":"{}"},"object_acl":{"default":"private"},"read_only":{"default":false},"requester_pays":{"default":false},"squash":{"default":"RootSquash"}}},"aws_storagegateway_smb_file_share":{"attributes":{"access_based_enumeration":{"default":false},"authentication":{"default":"ActiveDirectory"},"case_sensitivity":{"default":"ClientSpecified"},"default_storage_class":{"default":"S3_STANDARD"},"guess_mime_type_enabled":{"default":true},"kms_encrypted":{"default":false},"notification_policy":{"default":"{}"},"object_acl":{"default":"private"},"read_only":{"default":false},"requester_pays":{"default":false}}},"aws_storagegateway_tape_pool":{"attributes":{"retention_lock_time_in_days":{"default":0},"retention_lock_type":{"default":"NONE"}}},"aws_subnet":{"attributes":{"assign_ipv6_address_on_creation":{"default":false},"map_public_ip_on_launch":{"default":false}}},"aws_synthetics_canary":{"attributes":{"failure_retention_period":{"default":31},"run_config":{"elem":{"attributes":{"timeout_in_seconds":{"default":840}}}},"start_canary":{"default":false},"success_retention_period":{"default":31}}},"aws_transfer_server":{"attributes":{"endpoint_type":{"default":"PUBLIC"},"force_destroy":{"default":false},"identity_provider_type":{"default":"SERVICE_MANAGED"}}},"aws_transfer_user":{"attributes":{"home_directory_type":{"default":"PATH"}}},"aws_vpc":{"attributes":{"assign_generated_ipv6_cidr_block":{"default":false},"enable_dns_support":{"default":true},"instance_tenancy":{"default":"default"}}},"aws_vpc_endpoint":{"attributes":{"private_dns_enabled":{"default":false},"vpc_endpoint_type":{"default":"Gateway"}}},"aws_vpc_peering_connection":{"attributes":{"accepter":{"elem":{"attributes":{"allow_classic_link_to_remote_vpc":{"default":false},"allow_remote_vpc_dns_resolution":{"default":false},"allow_vpc_to_remote_classic_link":{"default":false}}}},"requester":{"elem":{"attributes":{"allow_classic_link_to_remote_vpc":{"default":false},"allow_remote_vpc_dns_resolution":{"default":false},"allow_vpc_to_remote_classic_link":{"default":false}}}}}},"aws_vpc_peering_connection_accepter":{"attributes":{"accepter":{"elem":{"attributes":{"allow_classic_link_to_remote_vpc":{"default":false},"allow_remote_vpc_dns_resolution":{"default":false},"allow_vpc_to_remote_classic_link":{"default":false}}}},"requester":{"elem":{"attributes":{"allow_classic_link_to_remote_vpc":{"default":false},"allow_remote_vpc_dns_resolution":{"default":false},"allow_vpc_to_remote_classic_link":{"default":false}}}}}},"aws_vpc_peering_connection_options":{"attributes":{"accepter":{"elem":{"attributes":{"allow_classic_link_to_remote_vpc":{"default":false},"allow_remote_vpc_dns_resolution":{"default":false},"allow_vpc_to_remote_classic_link":{"default":false}}}},"requester":{"elem":{"attributes":{"allow_classic_link_to_remote_vpc":{"default":false},"allow_remote_vpc_dns_resolution":{"default":false},"allow_vpc_to_remote_classic_link":{"default":false}}}}}},"aws_waf_rule_group":{"attributes":{"activated_rule":{"elem":{"attributes":{"type":{"default":"REGULAR"}}}}}},"aws_waf_web_acl":{"attributes":{"rules":{"elem":{"attributes":{"type":{"default":"REGULAR"}}}}}},"aws_wafregional_rule_group":{"attributes":{"activated_rule":{"elem":{"attributes":{"type":{"default":"REGULAR"}}}}}},"aws_wafregional_web_acl":{"attributes":{"rule":{"elem":{"attributes":{"type":{"default":"REGULAR"}}}}}},"aws_wafv2_web_acl":{"attributes":{"rule":{"elem":{"attributes":{"statement":{"elem":{"attributes":{"rate_based_statement":{"elem":{"attributes":{"aggregate_key_type":{"default":"IP"}}}}}}}}}}}},"aws_worklink_fleet":{"attributes":{"optimize_for_end_user_location":{"default":true}}},"aws_workspaces_directory":{"attributes":{"self_service_permissions":{"elem":{"attributes":{"change_compute_type":{"default":false},"increase_volume_size":{"default":false},"rebuild_workspace":{"default":false},"restart_workspace":{"default":true},"switch_running_mode":{"default":false}}}},"workspace_creation_properties":{"elem":{"attributes":{"enable_internet_access":{"default":false},"enable_maintenance_mode":{"default":false},"user_enabled_as_local_administrator":{"default":false}}}}}},"aws_workspaces_workspace":{"attributes":{"root_volume_encryption_enabled":{"default":false},"user_volume_encryption_enabled":{"default":false},"workspace_properties":{"elem":{"attributes":{"compute_type_name":{"default":"VALUE"},"root_volume_size_gib":{"default":80},"running_mode":{"default":"ALWAYS_ON"},"user_volume_size_gib":{"default":10}}}}}},"google_access_context_manager_access_level":{"attributes":{"basic":{"elem":{"attributes":{"combining_function":{"default":"AND"}}}}}},"google_access_context_manager_access_levels":{"attributes":{"access_levels":{"elem":{"attributes":{"basic":{"elem":{"attributes":{"combining_function":{"default":"AND"}}}}}}}}},"google_access_context_manager_service_perimeter":{"attributes":{"perimeter_type":{"default":"PERIMETER_TYPE_REGULAR"}}},"google_access_context_manager_service_perimeters":{"attributes":{"service_perimeters":{"elem":{"attributes":{"perimeter_type":{"default":"PERIMETER_TYPE_REGULAR"}}}}}},"google_active_directory_domain":{"attributes":{"admin":{"default":"setupadmin"}}},"google_apigee_organization":{"attributes":{"runtime_type":{"default":"CLOUD"}}},"google_app_engine_application":{"attributes":{"iap":{"elem":{"attributes":{"enabled":{"default":false}}}}}},"google_app_engine_application_url_dispatch_rules":{"attributes":{"dispatch_rules":{"elem":{"attributes":{"domain":{"default":"*"}}}}}},"google_app_engine_domain_mapping":{"attributes":{"override_strategy":{"default":"STRICT"}}},"google_app_engine_flexible_app_version":{"attributes":{"api_config":{"elem":{"attributes":{"auth_fail_action":{"default":"AUTH_FAIL_ACTION_REDIRECT"},"login":{"default":"LOGIN_OPTIONAL"}}}},"automatic_scaling":{"elem":{"attributes":{"cool_down_period":{"default":"120s"},"max_total_instances":{"default":20},"min_total_instances":{"default":2}}}},"delete_service_on_destroy":{"default":false},"endpoints_api_service":{"elem":{"attributes":{"disable_trace_sampling":{"default":false},"rollout_strategy":{"default":"FIXED"}}}},"handlers":{"elem":{"attributes":{"static_files":{"elem":{"attributes":{"expiration":{"default":"0s"}}}}}}},"liveness_check":{"elem":{"attributes":{"check_interval":{"default":"30s"},"failure_threshold":{"default":4},"initial_delay":{"default":"300s"},"success_threshold":{"default":2},"timeout":{"default":"4s"}}}},"noop_on_destroy":{"default":false},"readiness_check":{"elem":{"attributes":{"app_start_timeout":{"default":"300s"},"check_interval":{"default":"5s"},"failure_threshold":{"default":2},"success_threshold":{"default":2},"timeout":{"default":"4s"}}}},"serving_status":{"default":"SERVING"}}},"google_app_engine_standard_app_version":{"attributes":{"basic_scaling":{"elem":{"attributes":{"idle_timeout":{"default":"900s"}}}},"delete_service_on_destroy":{"default":false},"noop_on_destroy":{"default":false}}},"google_bigquery_data_transfer_config":{"attributes":{"location":{"default":"US"},"service_account_name":{"default":""}}},"google_bigquery_dataset":{"attributes":{"delete_contents_on_destroy":{"default":false},"location":{"default":"US"}}},"google_bigquery_job":{"attributes":{"copy":{"elem":{"attributes":{"create_disposition":{"default":"CREATE_IF_NEEDED"},"write_disposition":{"default":"WRITE_EMPTY"}}}},"extract":{"elem":{"attributes":{"compression":{"default":"NONE"},"print_header":{"default":true}}}},"load":{"elem":{"attributes":{"allow_jagged_rows":{"default":false},"allow_quoted_newlines":{"default":false},"create_disposition":{"default":"CREATE_IF_NEEDED"},"encoding":{"default":"UTF-8"},"ignore_unknown_values":{"default":false},"max_bad_records":{"default":0},"null_marker":{"default":""},"skip_leading_rows":{"default":0},"source_format":{"default":"CSV"},"write_disposition":{"default":"WRITE_EMPTY"}}}},"location":{"default":"US"},"query":{"elem":{"attributes":{"create_disposition":{"default":"CREATE_IF_NEEDED"},"priority":{"default":"INTERACTIVE"},"use_query_cache":{"default":true},"write_disposition":{"default":"WRITE_EMPTY"}}}}}},"google_bigquery_reservation":{"attributes":{"ignore_idle_slots":{"default":false},"location":{"default":"US"}}},"google_bigquery_routine":{"attributes":{"arguments":{"elem":{"attributes":{"argument_kind":{"default":"FIXED_TYPE"}}}}}},"google_bigquery_table":{"attributes":{"deletion_protection":{"default":true},"external_data_configuration":{"elem":{"attributes":{"compression":{"default":"NONE"},"csv_options":{"elem":{"attributes":{"allow_jagged_rows":{"default":false},"allow_quoted_newlines":{"default":false},"encoding":{"default":"UTF-8"},"field_delimiter":{"default":","},"skip_leading_rows":{"default":0}}}}}}},"materialized_view":{"elem":{"attributes":{"enable_refresh":{"default":true},"refresh_interval_ms":{"default":1800000}}}},"view":{"elem":{"attributes":{"use_legacy_sql":{"default":true}}}}}},"google_bigtable_app_profile":{"attributes":{"ignore_warnings":{"default":false}}},"google_bigtable_instance":{"attributes":{"cluster":{"elem":{"attributes":{"storage_type":{"default":"SSD"}}}},"deletion_protection":{"default":true},"instance_type":{"default":"PRODUCTION"}}},"google_billing_budget":{"attributes":{"all_updates_rule":{"elem":{"attributes":{"disable_default_iam_recipients":{"default":false},"schema_version":{"default":"1.0"}}}},"budget_filter":{"elem":{"attributes":{"credit_types_treatment":{"default":"INCLUDE_ALL_CREDITS"}}}},"threshold_rules":{"elem":{"attributes":{"spend_basis":{"default":"CURRENT_SPEND"}}}}}},"google_billing_subaccount":{"attributes":{"deletion_policy":{"default":""}}},"google_cloud_identity_group":{"attributes":{"initial_group_config":{"default":"EMPTY"}}},"google_cloud_run_domain_mapping":{"attributes":{"spec":{"elem":{"attributes":{"certificate_mode":{"default":"AUTOMATIC"}}}}}},"google_cloud_run_service":{"attributes":{"autogenerate_revision_name":{"default":false}}},"google_cloud_scheduler_job":{"attributes":{"attempt_deadline":{"default":"180s"},"time_zone":{"default":"Etc/UTC"}}},"google_cloudbuild_trigger":{"attributes":{"build":{"elem":{"attributes":{"timeout":{"default":"600s"}}}},"trigger_template":{"elem":{"attributes":{"repo_name":{"default":"default"}}}}}},"google_cloudfunctions_function":{"attributes":{"available_memory_mb":{"default":256},"ingress_settings":{"default":"ALLOW_ALL"},"max_instances":{"default":0},"timeout":{"default":60}}},"google_cloudiot_device":{"attributes":{"gateway_config":{"elem":{"attributes":{"gateway_type":{"default":"NON_GATEWAY"}}}}}},"google_cloudiot_registry":{"attributes":{"log_level":{"default":"NONE"}}},"google_composer_environment":{"attributes":{"config":{"elem":{"attributes":{"private_environment_config":{"elem":{"attributes":{"enable_private_endpoint":{"default":true}}}}}}}}},"google_compute_address":{"attributes":{"address_type":{"default":"EXTERNAL"}}},"google_compute_attached_disk":{"attributes":{"mode":{"default":"READ_WRITE"}}},"google_compute_autoscaler":{"attributes":{"autoscaling_policy":{"elem":{"attributes":{"cooldown_period":{"default":60},"mode":{"default":"ON"}}}}}},"google_compute_backend_service":{"attributes":{"backend":{"elem":{"attributes":{"balancing_mode":{"default":"UTILIZATION"},"capacity_scaler":{"default":1},"max_utilization":{"default":0.8}}}},"cdn_policy":{"elem":{"attributes":{"signed_url_cache_max_age_sec":{"default":3600}}}},"circuit_breakers":{"elem":{"attributes":{"max_connections":{"default":1024},"max_pending_requests":{"default":1024},"max_requests":{"default":1024},"max_retries":{"default":3}}}},"connection_draining_timeout_sec":{"default":300},"consistent_hash":{"elem":{"attributes":{"minimum_ring_size":{"default":1024}}}},"load_balancing_scheme":{"default":"EXTERNAL"},"outlier_detection":{"elem":{"attributes":{"consecutive_errors":{"default":5},"consecutive_gateway_failure":{"default":5},"enforcing_consecutive_errors":{"default":100},"enforcing_consecutive_gateway_failure":{"default":0},"enforcing_success_rate":{"default":100},"max_ejection_percent":{"default":10},"success_rate_minimum_hosts":{"default":5},"success_rate_request_volume":{"default":100},"success_rate_stdev_factor":{"default":1900}}}}}},"google_compute_disk":{"attributes":{"type":{"default":"pd-standard"}}},"google_compute_firewall":{"attributes":{"priority":{"default":1000}}},"google_compute_forwarding_rule":{"attributes":{"load_balancing_scheme":{"default":"EXTERNAL"}}},"google_compute_global_address":{"attributes":{"address_type":{"default":"EXTERNAL"}}},"google_compute_global_forwarding_rule":{"attributes":{"load_balancing_scheme":{"default":"EXTERNAL"}}},"google_compute_health_check":{"attributes":{"check_interval_sec":{"default":5},"healthy_threshold":{"default":2},"http2_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"},"request_path":{"default":"/"}}}},"http_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"},"request_path":{"default":"/"}}}},"https_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"},"request_path":{"default":"/"}}}},"ssl_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"}}}},"tcp_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"}}}},"timeout_sec":{"default":5},"unhealthy_threshold":{"default":2}}},"google_compute_http_health_check":{"attributes":{"check_interval_sec":{"default":5},"healthy_threshold":{"default":2},"port":{"default":80},"request_path":{"default":"/"},"timeout_sec":{"default":5},"unhealthy_threshold":{"default":2}}},"google_compute_https_health_check":{"attributes":{"check_interval_sec":{"default":5},"healthy_threshold":{"default":2},"port":{"default":443},"request_path":{"default":"/"},"timeout_sec":{"default":5},"unhealthy_threshold":{"default":2}}},"google_compute_image":{"attributes":{"raw_disk":{"elem":{"attributes":{"container_type":{"default":"TAR"}}}}}},"google_compute_instance":{"attributes":{"attached_disk":{"elem":{"attributes":{"mode":{"default":"READ_WRITE"}}}},"boot_disk":{"elem":{"attributes":{"auto_delete":{"default":true},"mode":{"default":"READ_WRITE"}}}},"can_ip_forward":{"default":false},"deletion_protection":{"default":false},"scheduling":{"elem":{"attributes":{"automatic_restart":{"default":true},"preemptible":{"default":false}}}},"shielded_instance_config":{"elem":{"attributes":{"enable_integrity_monitoring":{"default":true},"enable_secure_boot":{"default":false},"enable_vtpm":{"default":true}}}}}},"google_compute_instance_group_manager":{"attributes":{"stateful_disk":{"elem":{"attributes":{"delete_rule":{"default":"NEVER"}}}},"wait_for_instances":{"default":false}}},"google_compute_instance_template":{"attributes":{"can_ip_forward":{"default":false},"disk":{"elem":{"attributes":{"auto_delete":{"default":true}}}},"scheduling":{"elem":{"attributes":{"automatic_restart":{"default":true},"preemptible":{"default":false}}}},"shielded_instance_config":{"elem":{"attributes":{"enable_integrity_monitoring":{"default":true},"enable_secure_boot":{"default":false},"enable_vtpm":{"default":true}}}}}},"google_compute_interconnect_attachment":{"attributes":{"admin_enabled":{"default":true}}},"google_compute_managed_ssl_certificate":{"attributes":{"type":{"default":"MANAGED"}}},"google_compute_network":{"attributes":{"auto_create_subnetworks":{"default":true},"delete_default_routes_on_create":{"default":false}}},"google_compute_network_endpoint_group":{"attributes":{"network_endpoint_type":{"default":"GCE_VM_IP_PORT"}}},"google_compute_network_peering":{"attributes":{"export_custom_routes":{"default":false},"export_subnet_routes_with_public_ip":{"default":true},"import_custom_routes":{"default":false}}},"google_compute_node_group":{"attributes":{"maintenance_policy":{"default":"DEFAULT"}}},"google_compute_node_template":{"attributes":{"cpu_overcommit_type":{"default":"NONE"}}},"google_compute_packet_mirroring":{"attributes":{"filter":{"elem":{"attributes":{"direction":{"default":"BOTH"}}}}}},"google_compute_per_instance_config":{"attributes":{"minimal_action":{"default":"NONE"},"most_disruptive_allowed_action":{"default":"REPLACE"},"preserved_state":{"elem":{"attributes":{"disk":{"elem":{"attributes":{"delete_rule":{"default":"NEVER"},"mode":{"default":"READ_WRITE"}}}}}}},"remove_instance_state_on_destroy":{"default":false}}},"google_compute_region_autoscaler":{"attributes":{"autoscaling_policy":{"elem":{"attributes":{"cooldown_period":{"default":60},"mode":{"default":"ON"}}}}}},"google_compute_region_backend_service":{"attributes":{"backend":{"elem":{"attributes":{"balancing_mode":{"default":"CONNECTION"}}}},"cdn_policy":{"elem":{"attributes":{"signed_url_cache_max_age_sec":{"default":3600}}}},"circuit_breakers":{"elem":{"attributes":{"max_connections":{"default":1024},"max_pending_requests":{"default":1024},"max_requests":{"default":1024},"max_retries":{"default":3}}}},"connection_draining_timeout_sec":{"default":0},"consistent_hash":{"elem":{"attributes":{"minimum_ring_size":{"default":1024}}}},"load_balancing_scheme":{"default":"INTERNAL"},"outlier_detection":{"elem":{"attributes":{"consecutive_errors":{"default":5},"consecutive_gateway_failure":{"default":5},"enforcing_consecutive_errors":{"default":100},"enforcing_consecutive_gateway_failure":{"default":0},"enforcing_success_rate":{"default":100},"max_ejection_percent":{"default":10},"success_rate_minimum_hosts":{"default":5},"success_rate_request_volume":{"default":100},"success_rate_stdev_factor":{"default":1900}}}}}},"google_compute_region_disk":{"attributes":{"type":{"default":"pd-standard"}}},"google_compute_region_health_check":{"attributes":{"check_interval_sec":{"default":5},"healthy_threshold":{"default":2},"http2_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"},"request_path":{"default":"/"}}}},"http_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"},"request_path":{"default":"/"}}}},"https_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"},"request_path":{"default":"/"}}}},"ssl_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"}}}},"tcp_health_check":{"elem":{"attributes":{"proxy_header":{"default":"NONE"}}}},"timeout_sec":{"default":5},"unhealthy_threshold":{"default":2}}},"google_compute_region_instance_group_manager":{"attributes":{"stateful_disk":{"elem":{"attributes":{"delete_rule":{"default":"NEVER"}}}},"wait_for_instances":{"default":false}}},"google_compute_region_network_endpoint_group":{"attributes":{"network_endpoint_type":{"default":"SERVERLESS"}}},"google_compute_region_per_instance_config":{"attributes":{"minimal_action":{"default":"NONE"},"most_disruptive_allowed_action":{"default":"REPLACE"},"preserved_state":{"elem":{"attributes":{"disk":{"elem":{"attributes":{"delete_rule":{"default":"NEVER"},"mode":{"default":"READ_WRITE"}}}}}}},"remove_instance_state_on_destroy":{"default":false}}},"google_compute_region_url_map":{"attributes":{"default_url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false}}}},"path_matcher":{"elem":{"attributes":{"default_url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false}}}},"path_rule":{"elem":{"attributes":{"route_action":{"elem":{"attributes":{"cors_policy":{"elem":{"attributes":{"allow_credentials":{"default":false}}}}}}},"url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false}}}}}}},"route_rules":{"elem":{"attributes":{"match_rules":{"elem":{"attributes":{"header_matches":{"elem":{"attributes":{"invert_match":{"default":false}}}},"ignore_case":{"default":false}}}},"route_action":{"elem":{"attributes":{"cors_policy":{"elem":{"attributes":{"allow_credentials":{"default":false},"disabled":{"default":false}}}}}}},"url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false},"strip_query":{"default":false}}}}}}}}}}}},"google_compute_reservation":{"attributes":{"specific_reservation":{"elem":{"attributes":{"instance_properties":{"elem":{"attributes":{"local_ssds":{"elem":{"attributes":{"interface":{"default":"SCSI"}}}}}}}}}},"specific_reservation_required":{"default":false}}},"google_compute_resource_policy":{"attributes":{"snapshot_schedule_policy":{"elem":{"attributes":{"retention_policy":{"elem":{"attributes":{"on_source_disk_delete":{"default":"KEEP_AUTO_SNAPSHOTS"}}}}}}}}},"google_compute_route":{"attributes":{"priority":{"default":1000}}},"google_compute_router":{"attributes":{"bgp":{"elem":{"attributes":{"advertise_mode":{"default":"DEFAULT"}}}}}},"google_compute_router_nat":{"attributes":{"enable_endpoint_independent_mapping":{"default":true},"icmp_idle_timeout_sec":{"default":30},"tcp_established_idle_timeout_sec":{"default":1200},"tcp_transitory_idle_timeout_sec":{"default":30},"udp_idle_timeout_sec":{"default":30}}},"google_compute_router_peer":{"attributes":{"advertise_mode":{"default":"DEFAULT"}}},"google_compute_ssl_policy":{"attributes":{"min_tls_version":{"default":"TLS_1_0"},"profile":{"default":"COMPATIBLE"}}},"google_compute_subnetwork":{"attributes":{"log_config":{"elem":{"attributes":{"aggregation_interval":{"default":"INTERVAL_5_SEC"},"filter_expr":{"default":"true"},"flow_sampling":{"default":0.5},"metadata":{"default":"INCLUDE_ALL_METADATA"}}}}}},"google_compute_target_https_proxy":{"attributes":{"quic_override":{"default":"NONE"}}},"google_compute_target_instance":{"attributes":{"nat_policy":{"default":"NO_NAT"}}},"google_compute_target_pool":{"attributes":{"session_affinity":{"default":"NONE"}}},"google_compute_target_ssl_proxy":{"attributes":{"proxy_header":{"default":"NONE"}}},"google_compute_target_tcp_proxy":{"attributes":{"proxy_header":{"default":"NONE"}}},"google_compute_url_map":{"attributes":{"default_route_action":{"elem":{"attributes":{"cors_policy":{"elem":{"attributes":{"allow_credentials":{"default":false},"disabled":{"default":false}}}},"retry_policy":{"elem":{"attributes":{"num_retries":{"default":1}}}},"weighted_backend_services":{"elem":{"attributes":{"header_action":{"elem":{"attributes":{"request_headers_to_add":{"elem":{"attributes":{"replace":{"default":false}}}},"response_headers_to_add":{"elem":{"attributes":{"replace":{"default":false}}}}}}}}}}}}},"default_url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false}}}},"path_matcher":{"elem":{"attributes":{"default_route_action":{"elem":{"attributes":{"cors_policy":{"elem":{"attributes":{"allow_credentials":{"default":false},"disabled":{"default":false}}}},"retry_policy":{"elem":{"attributes":{"num_retries":{"default":1}}}},"weighted_backend_services":{"elem":{"attributes":{"header_action":{"elem":{"attributes":{"request_headers_to_add":{"elem":{"attributes":{"replace":{"default":false}}}},"response_headers_to_add":{"elem":{"attributes":{"replace":{"default":false}}}}}}}}}}}}},"default_url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false}}}},"path_rule":{"elem":{"attributes":{"route_action":{"elem":{"attributes":{"cors_policy":{"elem":{"attributes":{"allow_credentials":{"default":false}}}}}}},"url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false}}}}}}},"route_rules":{"elem":{"attributes":{"match_rules":{"elem":{"attributes":{"header_matches":{"elem":{"attributes":{"invert_match":{"default":false}}}},"ignore_case":{"default":false}}}},"route_action":{"elem":{"attributes":{"cors_policy":{"elem":{"attributes":{"allow_credentials":{"default":false},"disabled":{"default":false}}}}}}},"url_redirect":{"elem":{"attributes":{"https_redirect":{"default":false},"strip_query":{"default":false}}}}}}}}}}}},"google_compute_vpn_tunnel":{"attributes":{"ike_version":{"default":2}}},"google_container_cluster":{"attributes":{"cluster_autoscaling":{"elem":{"attributes":{"auto_provisioning_defaults":{"elem":{"attributes":{"service_account":{"default":"default"}}}}}}},"enable_binary_authorization":{"default":false},"enable_kubernetes_alpha":{"default":false},"enable_legacy_abac":{"default":false},"network":{"default":"default"},"network_policy":{"elem":{"attributes":{"provider":{"default":"PROVIDER_UNSPECIFIED"}}}},"node_config":{"elem":{"attributes":{"preemptible":{"default":false},"shielded_instance_config":{"elem":{"attributes":{"enable_integrity_monitoring":{"default":true},"enable_secure_boot":{"default":false}}}}}}},"node_pool":{"elem":{"attributes":{"management":{"elem":{"attributes":{"auto_repair":{"default":false},"auto_upgrade":{"default":false}}}},"node_config":{"elem":{"attributes":{"preemptible":{"default":false},"shielded_instance_config":{"elem":{"attributes":{"enable_integrity_monitoring":{"default":true},"enable_secure_boot":{"default":false}}}}}}}}}},"resource_usage_export_config":{"elem":{"attributes":{"enable_network_egress_metering":{"default":false},"enable_resource_consumption_metering":{"default":true}}}}}},"google_container_node_pool":{"attributes":{"management":{"elem":{"attributes":{"auto_repair":{"default":false},"auto_upgrade":{"default":false}}}},"node_config":{"elem":{"attributes":{"preemptible":{"default":false},"shielded_instance_config":{"elem":{"attributes":{"enable_integrity_monitoring":{"default":true},"enable_secure_boot":{"default":false}}}}}}}}},"google_data_catalog_tag_template":{"attributes":{"force_delete":{"default":false}}},"google_data_loss_prevention_inspect_template":{"attributes":{"inspect_config":{"elem":{"attributes":{"custom_info_types":{"elem":{"attributes":{"likelihood":{"default":"VERY_LIKELY"}}}},"min_likelihood":{"default":"POSSIBLE"}}}}}},"google_data_loss_prevention_job_trigger":{"attributes":{"status":{"default":"HEALTHY"}}},"google_dataflow_job":{"attributes":{"on_delete":{"default":"drain"}}},"google_dataproc_autoscaling_policy":{"attributes":{"basic_algorithm":{"elem":{"attributes":{"cooldown_period":{"default":"120s"},"yarn_config":{"elem":{"attributes":{"scale_down_min_worker_fraction":{"default":0},"scale_up_min_worker_fraction":{"default":0}}}}}}},"location":{"default":"global"},"secondary_worker_config":{"elem":{"attributes":{"max_instances":{"default":0},"min_instances":{"default":0},"weight":{"default":1}}}},"worker_config":{"elem":{"attributes":{"min_instances":{"default":2},"weight":{"default":1}}}}}},"google_dataproc_cluster":{"attributes":{"cluster_config":{"elem":{"attributes":{"gce_cluster_config":{"elem":{"attributes":{"internal_ip_only":{"default":false},"shielded_instance_config":{"elem":{"attributes":{"enable_integrity_monitoring":{"default":false},"enable_secure_boot":{"default":false},"enable_vtpm":{"default":false}}}}}}},"initialization_action":{"elem":{"attributes":{"timeout_sec":{"default":300}}}},"master_config":{"elem":{"attributes":{"disk_config":{"elem":{"attributes":{"boot_disk_type":{"default":"pd-standard"}}}}}}},"preemptible_worker_config":{"elem":{"attributes":{"disk_config":{"elem":{"attributes":{"boot_disk_type":{"default":"pd-standard"}}}}}}},"worker_config":{"elem":{"attributes":{"disk_config":{"elem":{"attributes":{"boot_disk_type":{"default":"pd-standard"}}}}}}}}}},"graceful_decommission_timeout":{"default":"0s"},"region":{"default":"global"}}},"google_dataproc_job":{"attributes":{"force_delete":{"default":false},"region":{"default":"global"}}},"google_datastore_index":{"attributes":{"ancestor":{"default":"NONE"}}},"google_deployment_manager_deployment":{"attributes":{"create_policy":{"default":"CREATE_OR_ACQUIRE"},"delete_policy":{"default":"DELETE"},"preview":{"default":false}}},"google_dns_managed_zone":{"attributes":{"description":{"default":"Managed by Terraform"},"dnssec_config":{"elem":{"attributes":{"default_key_specs":{"elem":{"attributes":{"kind":{"default":"dns#dnsKeySpec"}}}},"kind":{"default":"dns#managedZoneDnsSecConfig"}}}},"force_destroy":{"default":false},"visibility":{"default":"public"}}},"google_dns_policy":{"attributes":{"description":{"default":"Managed by Terraform"}}},"google_firestore_document":{"attributes":{"database":{"default":"(default)"}}},"google_firestore_index":{"attributes":{"database":{"default":"(default)"},"query_scope":{"default":"COLLECTION"}}},"google_folder_access_approval_settings":{"attributes":{"enrolled_services":{"elem":{"attributes":{"enrollment_level":{"default":"BLOCK_ALL"}}}}}},"google_folder_organization_policy":{"attributes":{"list_policy":{"elem":{"attributes":{"allow":{"elem":{"attributes":{"all":{"default":false}}}},"deny":{"elem":{"attributes":{"all":{"default":false}}}}}}}}},"google_game_services_game_server_cluster":{"attributes":{"location":{"default":"global"}}},"google_game_services_game_server_config":{"attributes":{"location":{"default":"global"}}},"google_game_services_game_server_deployment":{"attributes":{"location":{"default":"global"}}},"google_game_services_realm":{"attributes":{"location":{"default":"global"}}},"google_healthcare_fhir_store":{"attributes":{"stream_configs":{"elem":{"attributes":{"bigquery_destination":{"elem":{"attributes":{"schema_config":{"elem":{"attributes":{"schema_type":{"default":"ANALYTICS"}}}}}}}}}}}},"google_kms_crypto_key":{"attributes":{"purpose":{"default":"ENCRYPT_DECRYPT"},"version_template":{"elem":{"attributes":{"protection_level":{"default":"SOFTWARE"}}}}}},"google_logging_billing_account_bucket_config":{"attributes":{"retention_days":{"default":30}}},"google_logging_billing_account_sink":{"attributes":{"exclusions":{"elem":{"attributes":{"disabled":{"default":false}}}}}},"google_logging_folder_bucket_config":{"attributes":{"retention_days":{"default":30}}},"google_logging_folder_sink":{"attributes":{"exclusions":{"elem":{"attributes":{"disabled":{"default":false}}}},"include_children":{"default":false}}},"google_logging_metric":{"attributes":{"metric_descriptor":{"elem":{"attributes":{"labels":{"elem":{"attributes":{"value_type":{"default":"STRING"}}}},"unit":{"default":"1"}}}}}},"google_logging_organization_bucket_config":{"attributes":{"retention_days":{"default":30}}},"google_logging_organization_sink":{"attributes":{"exclusions":{"elem":{"attributes":{"disabled":{"default":false}}}},"include_children":{"default":false}}},"google_logging_project_bucket_config":{"attributes":{"retention_days":{"default":30}}},"google_logging_project_sink":{"attributes":{"exclusions":{"elem":{"attributes":{"disabled":{"default":false}}}},"unique_writer_identity":{"default":false}}},"google_memcache_instance":{"attributes":{"memcache_version":{"default":"MEMCACHE_1_5"}}},"google_monitoring_alert_policy":{"attributes":{"documentation":{"elem":{"attributes":{"mime_type":{"default":"text/markdown"}}}},"enabled":{"default":true}}},"google_monitoring_metric_descriptor":{"attributes":{"labels":{"elem":{"attributes":{"value_type":{"default":"STRING"}}}}}},"google_monitoring_notification_channel":{"attributes":{"enabled":{"default":true}}},"google_monitoring_slo":{"attributes":{"basic_sli":{"elem":{"attributes":{"availability":{"elem":{"attributes":{"enabled":{"default":true}}}}}}},"windows_based_sli":{"elem":{"attributes":{"good_total_ratio_threshold":{"elem":{"attributes":{"basic_sli_performance":{"elem":{"attributes":{"availability":{"elem":{"attributes":{"enabled":{"default":true}}}}}}}}}}}}}}},"google_monitoring_uptime_check_config":{"attributes":{"content_matchers":{"elem":{"attributes":{"matcher":{"default":"CONTAINS_STRING"}}}},"http_check":{"elem":{"attributes":{"path":{"default":"/"},"request_method":{"default":"GET"}}}},"period":{"default":"300s"}}},"google_network_management_connectivity_test":{"attributes":{"protocol":{"default":"TCP"}}},"google_notebooks_instance":{"attributes":{"shielded_instance_config":{"elem":{"attributes":{"enable_integrity_monitoring":{"default":true},"enable_vtpm":{"default":true}}}}}},"google_organization_access_approval_settings":{"attributes":{"enrolled_services":{"elem":{"attributes":{"enrollment_level":{"default":"BLOCK_ALL"}}}}}},"google_organization_iam_custom_role":{"attributes":{"stage":{"default":"GA"}}},"google_organization_policy":{"attributes":{"list_policy":{"elem":{"attributes":{"allow":{"elem":{"attributes":{"all":{"default":false}}}},"deny":{"elem":{"attributes":{"all":{"default":false}}}}}}}}},"google_project":{"attributes":{"auto_create_network":{"default":true}}},"google_project_access_approval_settings":{"attributes":{"enrolled_services":{"elem":{"attributes":{"enrollment_level":{"default":"BLOCK_ALL"}}}}}},"google_project_default_service_accounts":{"attributes":{"restore_policy":{"default":"REVERT"}}},"google_project_iam_custom_role":{"attributes":{"stage":{"default":"GA"}}},"google_project_organization_policy":{"attributes":{"list_policy":{"elem":{"attributes":{"allow":{"elem":{"attributes":{"all":{"default":false}}}},"deny":{"elem":{"attributes":{"all":{"default":false}}}}}}}}},"google_project_service":{"attributes":{"disable_on_destroy":{"default":true}}},"google_pubsub_subscription":{"attributes":{"message_retention_duration":{"default":"604800s"}}},"google_redis_instance":{"attributes":{"auth_enabled":{"default":false},"connect_mode":{"default":"DIRECT_PEERING"},"tier":{"default":"BASIC"}}},"google_secret_manager_secret_version":{"attributes":{"enabled":{"default":true}}},"google_service_account_key":{"attributes":{"key_algorithm":{"default":"KEY_ALG_RSA_2048"},"private_key_type":{"default":"TYPE_GOOGLE_CREDENTIALS_FILE"},"public_key_type":{"default":"TYPE_X509_PEM_FILE"}}},"google_spanner_database":{"attributes":{"deletion_protection":{"default":true}}},"google_spanner_instance":{"attributes":{"num_nodes":{"default":1}}},"google_sql_database_instance":{"attributes":{"database_version":{"default":"MYSQL_5_6"},"deletion_protection":{"default":true},"settings":{"elem":{"attributes":{"backup_configuration":{"elem":{"attributes":{"backup_retention_settings":{"elem":{"attributes":{"retention_unit":{"default":"COUNT"}}}}}}},"disk_autoresize":{"default":true},"insights_config":{"elem":{"attributes":{"query_string_length":{"default":1024}}}},"ip_configuration":{"elem":{"attributes":{"ipv4_enabled":{"default":true}}}},"pricing_plan":{"default":"PER_USE"}}}}}},"google_sql_source_representation_instance":{"attributes":{"port":{"default":3306}}},"google_storage_bucket":{"attributes":{"force_destroy":{"default":false},"location":{"default":"US"},"retention_policy":{"elem":{"attributes":{"is_locked":{"default":false}}}},"storage_class":{"default":"STANDARD"}}},"google_storage_bucket_object":{"attributes":{"detect_md5hash":{"default":"different hash"}}},"google_storage_hmac_key":{"attributes":{"state":{"default":"ACTIVE"}}},"google_storage_transfer_job":{"attributes":{"status":{"default":"ENABLED"}}},"google_tpu_node":{"attributes":{"use_service_networking":{"default":false}}},"google_vpc_access_connector":{"attributes":{"max_throughput":{"default":1000},"min_throughput":{"default":200}}}} \ No newline at end of file diff --git a/pkg/tf_resource_schemas/types.go b/pkg/tf_resource_schemas/types.go new file mode 100644 index 00000000..147af3c8 --- /dev/null +++ b/pkg/tf_resource_schemas/types.go @@ -0,0 +1,42 @@ +package tf_resource_schemas + +type ResourceSchemas map[string]*Schema + +type Schema struct { + Attributes map[string]*Schema `json:"attributes,omitempty"` + Elem *Schema `json:"elem,omitempty"` + Default interface{} `json:"default,omitempty"` +} + +func GetAttribute(schema *Schema, key string) *Schema { + if schema == nil || schema.Attributes == nil { + return nil + } + + if attr, ok := schema.Attributes[key]; ok { + return attr + } + + return nil +} + +func GetElem(schema *Schema) *Schema { + if schema == nil { + return nil + } + return schema.Elem +} + +func SetDefaultAttributes(schema *Schema, properties map[string]interface{}) { + if schema == nil || schema.Attributes == nil { + return + } + + for key, attr := range schema.Attributes { + if _, ok := properties[key]; !ok { + if attr.Default != nil { + properties[key] = attr.Default + } + } + } +} diff --git a/providers/terraform-provider-aws b/providers/terraform-provider-aws new file mode 160000 index 00000000..9a609f33 --- /dev/null +++ b/providers/terraform-provider-aws @@ -0,0 +1 @@ +Subproject commit 9a609f3305378574b57e5f1fbf695a38d66bf4b2 diff --git a/providers/terraform-provider-google b/providers/terraform-provider-google new file mode 160000 index 00000000..fb5f35b2 --- /dev/null +++ b/providers/terraform-provider-google @@ -0,0 +1 @@ +Subproject commit fb5f35b29250ca8976ae3d237d28a55b8f823299 diff --git a/rego/lib/fugue/input_type.rego b/rego/lib/fugue/input_type.rego index 026985e8..ef76d1d6 100644 --- a/rego/lib/fugue/input_type.rego +++ b/rego/lib/fugue/input_type.rego @@ -22,6 +22,8 @@ input_type = "terraform" { } terraform_input_type { + _ = input.hcl_resource_view_version +} { _ = input.terraform_version } { _ = input.resource_changes diff --git a/rego/lib/fugue/resource_view.rego b/rego/lib/fugue/resource_view.rego index f3d1a783..0763969c 100644 --- a/rego/lib/fugue/resource_view.rego +++ b/rego/lib/fugue/resource_view.rego @@ -21,18 +21,25 @@ import data.fugue.input_type import data.fugue.resource_view.cloudformation import data.fugue.resource_view.terraform -resource_view_input = ret { +resource_view = ret { + # If we are already given a resource view, just pass it through. + _ = input.hcl_resource_view_version + ret = input.resources +} else = ret { input_type.terraform_input_type - ret = {"resources": resource_view, "_plan": input} + ret = terraform.resource_view } else = ret { input_type.cloudformation_input_type - ret = {"resources": resource_view, "_template": input} + ret = cloudformation.resource_view } -resource_view = ret { +resource_view_input = ret { + _ = input.hcl_resource_view_version + ret = {"resources": resource_view} +} else = ret { input_type.terraform_input_type - ret = terraform.resource_view + ret = {"resources": resource_view, "_plan": input} } else = ret { input_type.cloudformation_input_type - ret = cloudformation.resource_view + ret = {"resources": resource_view, "_template": input} } diff --git a/rego/rules/tf/aws/iam/user_attached_policy.rego b/rego/rules/tf/aws/iam/user_attached_policy.rego index dcb40cd4..c5b9a8a3 100644 --- a/rego/rules/tf/aws/iam/user_attached_policy.rego +++ b/rego/rules/tf/aws/iam/user_attached_policy.rego @@ -52,8 +52,8 @@ is_invalid(resource) { resource = user_policy_attachments[name] } { resource = policy_attachments[name] - resource.users != null - resource.users != [""] + user = resource.users[_] + user != "" } policy[p] { diff --git a/rego/rules/tf/google/compute/subnet_flow_log_enabled.rego b/rego/rules/tf/google/compute/subnet_flow_log_enabled.rego index 62dc1501..1c56d95e 100644 --- a/rego/rules/tf/google/compute/subnet_flow_log_enabled.rego +++ b/rego/rules/tf/google/compute/subnet_flow_log_enabled.rego @@ -30,8 +30,8 @@ __rego__metadoc__ := { resource_type = "google_compute_subnetwork" -default deny = false +default allow = false -deny { - count(input.log_config) == 0 +allow { + _ = input.log_config[_] } diff --git a/rego/tests/rules/tf/azurerm/storage/container_private_access_test.rego b/rego/tests/rules/tf/azurerm/storage/container_private_access_test.rego index 02ade237..aa05096c 100644 --- a/rego/tests/rules/tf/azurerm/storage/container_private_access_test.rego +++ b/rego/tests/rules/tf/azurerm/storage/container_private_access_test.rego @@ -18,6 +18,5 @@ import data.tests.rules.tf.azurerm.storage.inputs.container_private_access_infra test_storage_container_private_access { resources = container_private_access_infra.mock_resources allow with input as resources["azurerm_storage_container.validcontainer1"] - allow with input as resources["azurerm_storage_container.validcontainer2"] not allow with input as resources["azurerm_storage_container.invalidcontainer1"] } diff --git a/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf index 8e243a89..eeafb493 100644 --- a/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf +++ b/rego/tests/rules/tf/azurerm/storage/inputs/container_private_access_infra.tf @@ -39,11 +39,6 @@ resource "azurerm_storage_container" "validcontainer1" { container_access_type = "private" } -resource "azurerm_storage_container" "validcontainer2" { - name = "validcontainer2" - storage_account_name = azurerm_storage_account.example.name -} - resource "azurerm_storage_container" "invalidcontainer1" { name = "invalidcontainer1" storage_account_name = azurerm_storage_account.example.name diff --git a/rego/tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego b/rego/tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego index 7b1cde9a..07211414 100644 --- a/rego/tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego +++ b/rego/tests/rules/tf/google/compute/subnet_flow_log_enabled_test.rego @@ -17,7 +17,7 @@ import data.tests.rules.tf.google.compute.inputs.subnet_flow_log_enabled_infra test_gcp_compute_subnet_flow_log_enabled { resources = subnet_flow_log_enabled_infra.mock_resources - not deny with input as resources["google_compute_subnetwork.valid-subnet-1"] - not deny with input as resources["google_compute_subnetwork.valid-subnet-2"] - deny with input as resources["google_compute_subnetwork.invalid-subnet-1"] + allow with input as resources["google_compute_subnetwork.valid-subnet-1"] + allow with input as resources["google_compute_subnetwork.valid-subnet-2"] + not allow with input as resources["google_compute_subnetwork.invalid-subnet-1"] } diff --git a/tools.go b/tools.go new file mode 100644 index 00000000..eeb9afa7 --- /dev/null +++ b/tools.go @@ -0,0 +1,11 @@ +// +build tools + +package tools + +import ( + // These are dependencies for `go generate`, see + // `pkg/tf_resource_schemas/generate/main.go`. + _ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + _ "github.com/terraform-providers/terraform-provider-aws/aws" + _ "github.com/terraform-providers/terraform-provider-google/google" +) From 1f6a9f5bb9a5780ac56f20b7bd79908142213352 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Thu, 20 May 2021 11:31:11 +0000 Subject: [PATCH 57/65] [RM-5431] Sneakily implement Data for renderContext --- go.sum | 3 ++ pkg/loader/tf.go | 129 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 20 deletions(-) diff --git a/go.sum b/go.sum index cd4f687b..d8c521d4 100644 --- a/go.sum +++ b/go.sum @@ -158,6 +158,7 @@ github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1U github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk= github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= @@ -400,6 +401,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= @@ -904,6 +906,7 @@ github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUA github.com/zclconf/go-cty v1.8.2 h1:u+xZfBKgpycDnTNjPhGiTEYZS5qS/Sb5MqSfm7vzcjg= github.com/zclconf/go-cty v1.8.2/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty-yaml v1.0.2 h1:dNyg4QLTrv2IfJpm7Wtxi55ed5gLGOlPrZ6kMd51hY0= github.com/zclconf/go-cty-yaml v1.0.2/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index 905c49f4..9ff7ee5c 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -24,7 +24,10 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs" + "github.com/hashicorp/terraform/lang" + "github.com/hashicorp/terraform/tfdiags" "github.com/zclconf/go-cty/cty" "tf_resource_schemas" @@ -36,8 +39,9 @@ func (t *TfDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfigurati if !strings.HasSuffix(i.Path(), ".tf") { return nil, fmt.Errorf("Expected a .tf extension for %s", i.Path()) } + dir := filepath.Dir(i.Path()) - return parseFiles([]string{}, nil, []string{i.Path()}) + return parseFiles([]string{}, dir, false, []string{i.Path()}) } func (t *TfDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) { @@ -55,9 +59,11 @@ type HclConfiguration struct { path []string // Directory the configuration is loaded from. Necessary to find the - // locations of child modules. If this is `nil`, it means the configuration - // was not loaded from a directory. - dir *string + // locations of child modules, and do `file()` calls. + dir string + + // Recursively load submodules. + recurse bool // Filepaths that have been loaded. filepaths []string @@ -88,22 +94,21 @@ func parseDirectory(path []string, dir string) (*HclConfiguration, error) { return nil, diags } - return parseFiles(path, &dir, primary) + return parseFiles(path, dir, true, primary) } -func parseFiles(path []string, dir *string, filepaths []string) (*HclConfiguration, error) { +func parseFiles(path []string, dir string, recurse bool, filepaths []string) (*HclConfiguration, error) { // Attempt to make filepaths relative, since we have the directory as well. for i := range filepaths { - if dir != nil { - if rel, err := filepath.Rel(*dir, filepaths[i]); err != nil { - filepaths[i] = rel - } + if rel, err := filepath.Rel(dir, filepaths[i]); err != nil { + filepaths[i] = rel } } configuration := new(HclConfiguration) configuration.path = path configuration.dir = dir + configuration.recurse = recurse configuration.filepaths = filepaths parser := configs.NewParser(nil) @@ -132,7 +137,7 @@ func parseFiles(path []string, dir *string, filepaths []string) (*HclConfigurati // Load children configuration.children = make(map[string]*HclConfiguration) - if dir != nil { + if recurse { for key, moduleCall := range module.ModuleCalls { fmt.Fprintf(os.Stderr, "Key: %s\n", key) body, ok := moduleCall.Config.(*hclsyntax.Body) @@ -140,13 +145,14 @@ func parseFiles(path []string, dir *string, filepaths []string) (*HclConfigurati // We're only interested in getting the `source` attribute, this // should not have any variables in it. ctx := renderContext{ + dir: dir, resolve: func(path []string) interface{} { return nil }, } properties := ctx.RenderBody(body) if source, ok := properties["source"]; ok { if str, ok := source.(string); ok { fmt.Fprintf(os.Stderr, "Loading submodule: %s\n", str) - childDir := filepath.Join(*dir, str) + childDir := filepath.Join(dir, str) // Construct child path, e.g. `module.child1.aws_vpc.child`. childPath := make([]string, len(configuration.path)) @@ -180,13 +186,13 @@ func (c0 *HclConfiguration) withVars(vars map[string]interface{}) *HclConfigurat func (c *HclConfiguration) LoadedFiles() []string { filepaths := []string{} - if c.dir != nil { - filepaths = append(filepaths, *c.dir) + if c.recurse { + filepaths = append(filepaths, c.dir) } fmt.Fprintf(os.Stderr, "%v\n", c.filepaths) for _, fp := range c.filepaths { - if c.dir != nil && !filepath.IsAbs(fp) { - fp = filepath.Join(*c.dir, fp) + if c.recurse && !filepath.IsAbs(fp) { + fp = filepath.Join(c.dir, fp) } filepaths = append(filepaths, fp) } @@ -204,8 +210,8 @@ func (c *HclConfiguration) Location(attributePath []string) (*Location, error) { func (c *HclConfiguration) RegulaInput() RegulaInput { path := "" - if c.dir != nil { - path = *c.dir + if c.recurse { + path = c.dir } else { path = c.filepaths[0] } @@ -359,6 +365,7 @@ func (c *HclConfiguration) GetChild(name string) (*HclConfiguration, bool) { body, ok := moduleCall.Config.(*hclsyntax.Body) if ok { ctx := renderContext{ + dir: c.dir, resolve: func(path []string) interface{} { return c.resolveResourceReference(path) }, } childVars = ctx.RenderBody(body) @@ -398,6 +405,7 @@ func (c *HclConfiguration) renderContext() renderContext { // This is a structure passed down that contains all additional information // apart from the thing being rendered, which is passed separately. type renderContext struct { + dir string schema *tf_resource_schemas.Schema resolve func([]string) interface{} } @@ -557,8 +565,26 @@ func (c *renderContext) RenderExpr(expr hclsyntax.Expression) interface{} { } func (c *renderContext) EvaluateExpr(expr hcl.Expression) interface{} { - ctx := hcl.EvalContext{} - val, _ := expr.Value(&ctx) + // We set up a scope and context to be close to regula terraform, and we + // reuse the functions that it exposes. + scope := lang.Scope{ + Data: c, + BaseDir: c.dir, + SelfAddr: nil, + PureOnly: false, + } + // NOTE: we could try to convert the variables we have into native cty.Value + // items and insert them again as variables. + vars := make(map[string]cty.Value) + ctx := hcl.EvalContext{ + Functions: scope.Functions(), + Variables: vars, + } + + val, err := expr.Value(&ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "Evaluation error: %s\n", err) + } return c.RenderValue(val) } @@ -612,9 +638,72 @@ func (c *renderContext) RenderValue(val cty.Value) interface{} { } fmt.Fprintf(os.Stderr, "Unknown type: %v\n", val.Type().GoString()) + fmt.Fprintf(os.Stderr, "Wholly known: %v\n", val.HasWhollyKnownType()) + return nil +} + +//////////////////////////////////////////////////////////////////////////////// +// This implements the lang.Data interface on the renderContext. Most of the +// functions we're interested in do not use lang.Data, but we can't use a `nil`. + +type UnsupportedOperationDiag struct { +} + +func (d UnsupportedOperationDiag) Severity() tfdiags.Severity { + return tfdiags.Error +} + +func (d UnsupportedOperationDiag) Description() tfdiags.Description { + return tfdiags.Description{ + Summary: "Unsupported operation", + Detail: "This operation cannot currently be performed by regula.", + } +} + +func (d UnsupportedOperationDiag) Source() tfdiags.Source { + return tfdiags.Source{} +} + +func (d UnsupportedOperationDiag) FromExpr() *tfdiags.FromExpr { return nil } +func (c *renderContext) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics { + return tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetCountAttr(addrs.CountAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetForEachAttr(addrs.ForEachAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetResource(addrs.Resource, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetLocalValue(addrs.LocalValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetModule(addrs.ModuleCall, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetPathAttr(addrs.PathAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetTerraformAttr(addrs.TerraformAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + +func (c *renderContext) GetInputVariable(addrs.InputVariable, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { + return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} +} + //////////////////////////////////////////////////////////////////////////////// // utilities for traversing to a path in a HCL tree somewhat generically From f759cf47bab9e55b3817c9b9d91dd3f529d41cc4 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Thu, 20 May 2021 11:56:44 +0000 Subject: [PATCH 58/65] [RM-5431] PR suggestions --- pkg/loader/tf.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index 9ff7ee5c..b8cb8f5a 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -36,7 +36,7 @@ import ( type TfDetector struct{} func (t *TfDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) { - if !strings.HasSuffix(i.Path(), ".tf") { + if i.Ext() != ".tf" { return nil, fmt.Errorf("Expected a .tf extension for %s", i.Path()) } dir := filepath.Dir(i.Path()) @@ -46,7 +46,13 @@ func (t *TfDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfigurati func (t *TfDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) { // First check that a `.tf` file exists in the directory. - if matches, err := filepath.Glob(i.Path() + "/*.tf"); err != nil || len(matches) == 0 { + tfExists := false + for _, child := range i.Children() { + if c, ok := child.(InputFile); ok && c.Ext() == ".tf" { + tfExists = true + } + } + if !tfExists { return nil, fmt.Errorf("Directory does not contain a .tf file: %s", i.Path()) } From 07087cd5c38f1cb47209d5d43035ef06fa7e9088 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Thu, 20 May 2021 11:59:08 +0000 Subject: [PATCH 59/65] [RM-5431] Silence warning --- pkg/loader/tf.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index b8cb8f5a..e6a10b65 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -562,10 +562,12 @@ func (c *renderContext) RenderExpr(expr hclsyntax.Expression) interface{} { } else { return c.RenderExpr(e.Wrapped) } + case *hclsyntax.FunctionCallExpr: + // This is handled using evaluation. + default: + fmt.Fprintf(os.Stderr, "warning: unhandled expression type %s\n", reflect.TypeOf(expr).String()) } - fmt.Fprintf(os.Stderr, "warning: unhandled expression type %s\n", reflect.TypeOf(expr).String()) - // Fall back to normal eval. return c.EvaluateExpr(expr) } From a0ae1a65daae0e5860c70563316721def75de554 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Sat, 22 May 2021 09:23:03 +0000 Subject: [PATCH 60/65] [RM-5431] Don't relativize filepaths --- pkg/loader/tf.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index e6a10b65..916834a5 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -104,13 +104,6 @@ func parseDirectory(path []string, dir string) (*HclConfiguration, error) { } func parseFiles(path []string, dir string, recurse bool, filepaths []string) (*HclConfiguration, error) { - // Attempt to make filepaths relative, since we have the directory as well. - for i := range filepaths { - if rel, err := filepath.Rel(dir, filepaths[i]); err != nil { - filepaths[i] = rel - } - } - configuration := new(HclConfiguration) configuration.path = path configuration.dir = dir @@ -197,9 +190,6 @@ func (c *HclConfiguration) LoadedFiles() []string { } fmt.Fprintf(os.Stderr, "%v\n", c.filepaths) for _, fp := range c.filepaths { - if c.recurse && !filepath.IsAbs(fp) { - fp = filepath.Join(c.dir, fp) - } filepaths = append(filepaths, fp) } for _, child := range c.children { From 472608e6ebf578db5334b241e7655f7c3e19ba3c Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Mon, 24 May 2021 16:08:00 +0000 Subject: [PATCH 61/65] [RM-5431] Support local references, add tf tests --- .gitmodules | 3 + pkg/loader/tf.go | 40 ++++++++---- pkg/loader/tf_test.go | 61 +++++++++++++++++++ pkg/loader/tf_test/example-terraform-modules | 1 + .../tf_test/example-terraform-modules.json | 50 +++++++++++++++ pkg/loader/tf_test/resource-local-ref.json | 18 ++++++ pkg/loader/tf_test/resource-local-ref/main.tf | 7 +++ 7 files changed, 167 insertions(+), 13 deletions(-) create mode 100644 pkg/loader/tf_test.go create mode 160000 pkg/loader/tf_test/example-terraform-modules create mode 100644 pkg/loader/tf_test/example-terraform-modules.json create mode 100644 pkg/loader/tf_test/resource-local-ref.json create mode 100644 pkg/loader/tf_test/resource-local-ref/main.tf diff --git a/.gitmodules b/.gitmodules index d78feb04..f0645f64 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "providers/terraform-provider-aws"] path = providers/terraform-provider-aws url = https://github.com/hashicorp/terraform-provider-aws.git +[submodule "pkg/loader/tf_test/example-terraform-modules"] + path = pkg/loader/tf_test/example-terraform-modules + url = https://github.com/jaspervdj-luminal/example-terraform-modules.git diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index 916834a5..b88ea6f2 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -56,7 +56,7 @@ func (t *TfDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACC return nil, fmt.Errorf("Directory does not contain a .tf file: %s", i.Path()) } - return parseDirectory([]string{}, i.Path()) + return ParseDirectory([]string{}, i.Path()) } type HclConfiguration struct { @@ -91,7 +91,7 @@ type HclConfiguration struct { vars map[string]interface{} } -func parseDirectory(path []string, dir string) (*HclConfiguration, error) { +func ParseDirectory(path []string, dir string) (*HclConfiguration, error) { parser := configs.NewParser(nil) var diags hcl.Diagnostics @@ -159,7 +159,7 @@ func parseFiles(path []string, dir string, recurse bool, filepaths []string) (*H childPath = append(childPath, "module") childPath = append(childPath, key) - if child, err := parseDirectory(childPath, childDir); err == nil { + if child, err := ParseDirectory(childPath, childDir); err == nil { configuration.children[key] = child } else { fmt.Fprintf(os.Stderr, "warning: Error loading submodule: %s\n", err) @@ -259,7 +259,7 @@ func (c *HclConfiguration) renderResources() map[string]interface{} { func (c *HclConfiguration) renderResource( resourceId string, resource *configs.Resource, ) interface{} { - context := c.renderContext() + context := c.renderContext(resourceId) context.schema = c.schemas[resource.Type] properties := make(map[string]interface{}) @@ -297,7 +297,7 @@ func (c *HclConfiguration) getResource(id string) (*configs.Resource, bool) { return nil, false } -func (c *HclConfiguration) resolveResourceReference(path []string) interface{} { +func (c *HclConfiguration) resolveResourceReference(self string, path []string) interface{} { if len(path) < 1 { return nil } @@ -333,16 +333,28 @@ func (c *HclConfiguration) resolveResourceReference(path []string) interface{} { return nil } - resourceId := strings.Join(path[:idx], ".") + resourceId := "" + attributePath := []string{} + + if len(path) == 1 { + // Assume it is a local reference and update `path` to point to this + // reference inside `self`. + resourceId = self + attributePath = []string{path[0]} + } else { + resourceId = strings.Join(path[:idx], ".") + attributePath = path[idx:] + } + if resource, ok := c.getResource(resourceId); ok { resourceNode := TfNode{Object: resource.Config, Range: resource.DeclRange} - if node, err := resourceNode.GetDescendant(path[idx:]); err == nil { + if node, err := resourceNode.GetDescendant(attributePath); err == nil { // NOTE: We could handle non-attribute cases but we're usually not // using these to work with lists and blocks. if node.Attribute != nil { expr := node.Attribute.Expr if e, ok := expr.(hclsyntax.Expression); ok { - ctx := c.renderContext() + ctx := c.renderContext(resourceId) return ctx.RenderExpr(e) } } @@ -361,8 +373,10 @@ func (c *HclConfiguration) GetChild(name string) (*HclConfiguration, bool) { body, ok := moduleCall.Config.(*hclsyntax.Body) if ok { ctx := renderContext{ - dir: c.dir, - resolve: func(path []string) interface{} { return c.resolveResourceReference(path) }, + dir: c.dir, + resolve: func(path []string) interface{} { + return c.resolveResourceReference("module."+name, path) + }, } childVars = ctx.RenderBody(body) for key, val := range childVars { @@ -384,7 +398,7 @@ func (c *HclConfiguration) GetChild(name string) (*HclConfiguration, bool) { func (c *HclConfiguration) GetOutput(name string) interface{} { if output, ok := c.module.Outputs[name]; ok { if e, ok := output.Expr.(hclsyntax.Expression); ok { - ctx := c.renderContext() + ctx := c.renderContext("") return ctx.RenderExpr(e) } } @@ -392,9 +406,9 @@ func (c *HclConfiguration) GetOutput(name string) interface{} { return nil } -func (c *HclConfiguration) renderContext() renderContext { +func (c *HclConfiguration) renderContext(self string) renderContext { return renderContext{ - resolve: func(path []string) interface{} { return c.resolveResourceReference(path) }, + resolve: func(path []string) interface{} { return c.resolveResourceReference(self, path) }, } } diff --git a/pkg/loader/tf_test.go b/pkg/loader/tf_test.go new file mode 100644 index 00000000..c275e19f --- /dev/null +++ b/pkg/loader/tf_test.go @@ -0,0 +1,61 @@ +package loader_test + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/fugue/regula/pkg/loader" + "github.com/stretchr/testify/assert" +) + +func TestTf(t *testing.T) { + testDir := "tf_test" + + c, err := ioutil.ReadDir(testDir) + if err != nil { + t.Fatal(err) + } + + fixTests := false + for _, arg := range os.Args { + if arg == "tf-test-fix" { + fixTests = true + } + } + + for _, entry := range c { + if entry.IsDir() { + dir := filepath.Join(testDir, entry.Name()) + outputPath := filepath.Join(testDir, entry.Name()+".json") + + hcl, err := loader.ParseDirectory([]string{}, dir) + if err != nil { + t.Fatal(err) + } + + actualBytes, err := json.MarshalIndent(hcl.RegulaInput(), "", " ") + if err != nil { + t.Fatal(err) + } + + expectedBytes := []byte{} + if _, err := os.Stat(outputPath); err == nil { + expectedBytes, _ = ioutil.ReadFile(outputPath) + if err != nil { + t.Fatal(err) + } + } + + actual := string(actualBytes) + expected := string(expectedBytes) + assert.Equal(t, expected, actual) + + if fixTests { + ioutil.WriteFile(outputPath, actualBytes, 0644) + } + } + } +} diff --git a/pkg/loader/tf_test/example-terraform-modules b/pkg/loader/tf_test/example-terraform-modules new file mode 160000 index 00000000..89a426dc --- /dev/null +++ b/pkg/loader/tf_test/example-terraform-modules @@ -0,0 +1 @@ +Subproject commit 89a426dc646af4484abe9149da40a97fd165a32a diff --git a/pkg/loader/tf_test/example-terraform-modules.json b/pkg/loader/tf_test/example-terraform-modules.json new file mode 100644 index 00000000..05c0722c --- /dev/null +++ b/pkg/loader/tf_test/example-terraform-modules.json @@ -0,0 +1,50 @@ +{ + "content": { + "hcl_resource_view_version": "0.0.1", + "resources": { + "aws_security_group.parent": { + "_provider": "hashicorp/aws", + "_type": "aws_security_group", + "id": "aws_security_group.parent", + "vpc_id": "module.child1.module.grandchild1.aws_vpc.grandchild" + }, + "aws_vpc.parent": { + "_provider": "hashicorp/aws", + "_type": "aws_vpc", + "cidr_block": "10.0.0.0/16", + "id": "aws_vpc.parent" + }, + "module.child1.aws_vpc.child": { + "_provider": "hashicorp/aws", + "_type": "aws_vpc", + "cidr_block": "10.0.0.0/16", + "id": "module.child1.aws_vpc.child" + }, + "module.child1.module.grandchild1.aws_security_group.grandchild": { + "_provider": "hashicorp/aws", + "_type": "aws_security_group", + "id": "module.child1.module.grandchild1.aws_security_group.grandchild", + "vpc_id": "module.child1.module.grandchild1.aws_vpc.grandchild" + }, + "module.child1.module.grandchild1.aws_vpc.grandchild": { + "_provider": "hashicorp/aws", + "_type": "aws_vpc", + "cidr_block": "10.0.0.0/16", + "id": "module.child1.module.grandchild1.aws_vpc.grandchild" + }, + "module.child2.aws_security_group.child": { + "_provider": "hashicorp/aws", + "_type": "aws_security_group", + "id": "module.child2.aws_security_group.child", + "vpc_id": "module.child1.module.grandchild1.aws_vpc.grandchild" + }, + "module.child2.aws_vpc.child": { + "_provider": "hashicorp/aws", + "_type": "aws_vpc", + "cidr_block": "10.0.0.0/16", + "id": "module.child2.aws_vpc.child" + } + } + }, + "filepath": "tf_test/example-terraform-modules" +} \ No newline at end of file diff --git a/pkg/loader/tf_test/resource-local-ref.json b/pkg/loader/tf_test/resource-local-ref.json new file mode 100644 index 00000000..909f624e --- /dev/null +++ b/pkg/loader/tf_test/resource-local-ref.json @@ -0,0 +1,18 @@ +{ + "content": { + "hcl_resource_view_version": "0.0.1", + "resources": { + "aws_s3_bucket.trail_bucket": { + "_provider": "hashicorp/aws", + "_type": "aws_s3_bucket", + "bucket_prefix": "hello", + "force_destroy": true, + "id": "aws_s3_bucket.trail_bucket", + "tags": { + "prefix": "hello" + } + } + } + }, + "filepath": "tf_test/resource-local-ref" +} \ No newline at end of file diff --git a/pkg/loader/tf_test/resource-local-ref/main.tf b/pkg/loader/tf_test/resource-local-ref/main.tf new file mode 100644 index 00000000..c4fe523d --- /dev/null +++ b/pkg/loader/tf_test/resource-local-ref/main.tf @@ -0,0 +1,7 @@ +resource "aws_s3_bucket" "trail_bucket" { + bucket_prefix = "hello" + force_destroy = true + tags = { + prefix = bucket_prefix + } +} From f7ee8495244f5b341fe03e121b7727bd7af83225 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Mon, 24 May 2021 17:25:12 +0000 Subject: [PATCH 62/65] [RM-5431] Improve file() support, add tests --- pkg/loader/tf.go | 10 +++++++--- pkg/loader/tf_test/file.json | 18 ++++++++++++++++++ pkg/loader/tf_test/file/hello.txt | 1 + pkg/loader/tf_test/file/main.tf | 7 +++++++ 4 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 pkg/loader/tf_test/file.json create mode 100644 pkg/loader/tf_test/file/hello.txt create mode 100644 pkg/loader/tf_test/file/main.tf diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index b88ea6f2..ffa698b1 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -408,6 +408,7 @@ func (c *HclConfiguration) GetOutput(name string) interface{} { func (c *HclConfiguration) renderContext(self string) renderContext { return renderContext{ + dir: c.dir, resolve: func(path []string) interface{} { return c.resolveResourceReference(self, path) }, } } @@ -581,13 +582,16 @@ func (c *renderContext) EvaluateExpr(expr hcl.Expression) interface{} { // reuse the functions that it exposes. scope := lang.Scope{ Data: c, - BaseDir: c.dir, SelfAddr: nil, PureOnly: false, } // NOTE: we could try to convert the variables we have into native cty.Value // items and insert them again as variables. - vars := make(map[string]cty.Value) + vars := map[string]cty.Value{ + "path": cty.MapVal(map[string]cty.Value{ + "module": cty.StringVal(c.dir), + }), + } ctx := hcl.EvalContext{ Functions: scope.Functions(), Variables: vars, @@ -704,7 +708,7 @@ func (c *renderContext) GetModule(addrs.ModuleCall, tfdiags.SourceRange) (cty.Va return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} } -func (c *renderContext) GetPathAttr(addrs.PathAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { +func (c *renderContext) GetPathAttr(attr addrs.PathAttr, diags tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) { return cty.UnknownVal(cty.DynamicPseudoType), tfdiags.Diagnostics{UnsupportedOperationDiag{}} } diff --git a/pkg/loader/tf_test/file.json b/pkg/loader/tf_test/file.json new file mode 100644 index 00000000..5bf4e14e --- /dev/null +++ b/pkg/loader/tf_test/file.json @@ -0,0 +1,18 @@ +{ + "content": { + "hcl_resource_view_version": "0.0.1", + "resources": { + "aws_s3_bucket.trail_bucket": { + "_provider": "hashicorp/aws", + "_type": "aws_s3_bucket", + "force_destroy": true, + "id": "aws_s3_bucket.trail_bucket", + "tags": { + "file1": "Hello\n", + "file2": "Hello\n" + } + } + } + }, + "filepath": "tf_test/file" +} \ No newline at end of file diff --git a/pkg/loader/tf_test/file/hello.txt b/pkg/loader/tf_test/file/hello.txt new file mode 100644 index 00000000..e965047a --- /dev/null +++ b/pkg/loader/tf_test/file/hello.txt @@ -0,0 +1 @@ +Hello diff --git a/pkg/loader/tf_test/file/main.tf b/pkg/loader/tf_test/file/main.tf new file mode 100644 index 00000000..3dbe9533 --- /dev/null +++ b/pkg/loader/tf_test/file/main.tf @@ -0,0 +1,7 @@ +resource "aws_s3_bucket" "trail_bucket" { + force_destroy = true + tags = { + file1 = file("tf_test/file/hello.txt") + file2 = file("${path.module}/hello.txt") + } +} From 48b02d77623bdd3d1fd2cda66aa978cfa4b36f33 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Mon, 24 May 2021 18:23:07 +0000 Subject: [PATCH 63/65] [RM-5431] Parse .terraform/modules/modules.json --- pkg/loader/tf.go | 81 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 6 deletions(-) diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index ffa698b1..a3ddd49d 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -15,7 +15,9 @@ package loader import ( + "encoding/json" "fmt" + "io/ioutil" "os" "path/filepath" "reflect" @@ -41,7 +43,7 @@ func (t *TfDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfigurati } dir := filepath.Dir(i.Path()) - return parseFiles([]string{}, dir, false, []string{i.Path()}) + return parseFiles([]string{}, dir, nil, false, []string{i.Path()}) } func (t *TfDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACConfiguration, error) { @@ -56,7 +58,7 @@ func (t *TfDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACC return nil, fmt.Errorf("Directory does not contain a .tf file: %s", i.Path()) } - return ParseDirectory([]string{}, i.Path()) + return ParseDirectory([]string{}, i.Path(), nil) } type HclConfiguration struct { @@ -89,9 +91,16 @@ type HclConfiguration struct { // Values of variables. Maybe we should make this lazy to handle cycles // better? vars map[string]interface{} + + // Locations of terraform modules. + moduleRegister *terraformModuleRegister } -func ParseDirectory(path []string, dir string) (*HclConfiguration, error) { +func ParseDirectory( + path []string, + dir string, + moduleRegister *terraformModuleRegister, +) (*HclConfiguration, error) { parser := configs.NewParser(nil) var diags hcl.Diagnostics @@ -100,16 +109,28 @@ func ParseDirectory(path []string, dir string) (*HclConfiguration, error) { return nil, diags } - return parseFiles(path, dir, true, primary) + return parseFiles(path, dir, moduleRegister, true, primary) } -func parseFiles(path []string, dir string, recurse bool, filepaths []string) (*HclConfiguration, error) { +func parseFiles( + path []string, + dir string, + moduleRegister *terraformModuleRegister, + recurse bool, + filepaths []string, +) (*HclConfiguration, error) { configuration := new(HclConfiguration) configuration.path = path configuration.dir = dir configuration.recurse = recurse configuration.filepaths = filepaths + if moduleRegister == nil { + configuration.moduleRegister = newTerraformRegister(dir) + } else { + configuration.moduleRegister = moduleRegister + } + parser := configs.NewParser(nil) var diags hcl.Diagnostics parsedFiles := make([]*configs.File, 0) @@ -152,6 +173,9 @@ func parseFiles(path []string, dir string, recurse bool, filepaths []string) (*H if str, ok := source.(string); ok { fmt.Fprintf(os.Stderr, "Loading submodule: %s\n", str) childDir := filepath.Join(dir, str) + if register := configuration.moduleRegister.getDir(str); register != nil { + childDir = filepath.Join(dir, *register) + } // Construct child path, e.g. `module.child1.aws_vpc.child`. childPath := make([]string, len(configuration.path)) @@ -159,7 +183,8 @@ func parseFiles(path []string, dir string, recurse bool, filepaths []string) (*H childPath = append(childPath, "module") childPath = append(childPath, key) - if child, err := ParseDirectory(childPath, childDir); err == nil { + child, err := ParseDirectory(childPath, childDir, configuration.moduleRegister) + if err == nil { configuration.children[key] = child } else { fmt.Fprintf(os.Stderr, "warning: Error loading submodule: %s\n", err) @@ -624,6 +649,10 @@ func (c *renderContext) RenderTraversal(traversal hcl.Traversal) []string { } func (c *renderContext) RenderValue(val cty.Value) interface{} { + if !val.IsKnown() { + return nil + } + if val.Type() == cty.Bool { return val.True() } else if val.Type() == cty.Number { @@ -803,3 +832,43 @@ func (node *TfNode) Location() string { node.Range.Start.Column, ) } + +//////////////////////////////////////////////////////////////////////////////// +// utilities for traversing to a path in a HCL tree somewhat generically +// `terraform init` downloads modules and writes a helpful file +// `.terraform/modules/modules.json` that tells us where to find modules + +//{"Modules":[{"Key":"","Source":"","Dir":"."},{"Key":"acm","Source":"terraform-aws-modules/acm/aws","Version":"3.0.0","Dir":".terraform/modules/acm"}]} +type terraformModuleRegister struct { + Modules []terraformModuleRegisterEntry `json:"Modules"` +} + +type terraformModuleRegisterEntry struct { + Source string `json:"Source"` + Dir string `json:"Dir"` +} + +func newTerraformRegister(dir string) *terraformModuleRegister { + registry := terraformModuleRegister{ + Modules: []terraformModuleRegisterEntry{}, + } + path := filepath.Join(dir, ".terraform/modules/modules.json") + bytes, err := ioutil.ReadFile(path) + if err != nil { + return ®istry + } + json.Unmarshal(bytes, ®istry) + for _, entry := range registry.Modules { + fmt.Fprintf(os.Stderr, "Entry: %s -> %s", entry.Source, entry.Dir) + } + return ®istry +} + +func (r *terraformModuleRegister) getDir(source string) *string { + for _, entry := range r.Modules { + if entry.Source == source { + return &entry.Dir + } + } + return nil +} From 370b898c816e93d4a899fb457e4f781a75e575e5 Mon Sep 17 00:00:00 2001 From: Jasper Van der Jeugt Date: Mon, 24 May 2021 18:40:35 +0000 Subject: [PATCH 64/65] [RM-5431] Detector fixes --- pkg/loader/tf.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/loader/tf.go b/pkg/loader/tf.go index a3ddd49d..c388833b 100644 --- a/pkg/loader/tf.go +++ b/pkg/loader/tf.go @@ -38,7 +38,7 @@ import ( type TfDetector struct{} func (t *TfDetector) DetectFile(i InputFile, opts DetectOptions) (IACConfiguration, error) { - if i.Ext() != ".tf" { + if !opts.IgnoreExt && i.Ext() != ".tf" { return nil, fmt.Errorf("Expected a .tf extension for %s", i.Path()) } dir := filepath.Dir(i.Path()) @@ -55,7 +55,7 @@ func (t *TfDetector) DetectDirectory(i InputDirectory, opts DetectOptions) (IACC } } if !tfExists { - return nil, fmt.Errorf("Directory does not contain a .tf file: %s", i.Path()) + return nil, nil } return ParseDirectory([]string{}, i.Path(), nil) From 6eb16b848c80c297e0a2c92fd5d325b5b117375d Mon Sep 17 00:00:00 2001 From: Curtis Myzie Date: Mon, 24 May 2021 22:08:17 -0400 Subject: [PATCH 65/65] Draft of markdown generation from rules metadata --- scripts/docs/.gitignore | 3 + scripts/docs/Makefile | 12 ++++ scripts/docs/requirements.txt | 1 + scripts/docs/rules_tables.py | 129 ++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 scripts/docs/.gitignore create mode 100644 scripts/docs/Makefile create mode 100644 scripts/docs/requirements.txt create mode 100644 scripts/docs/rules_tables.py diff --git a/scripts/docs/.gitignore b/scripts/docs/.gitignore new file mode 100644 index 00000000..48040050 --- /dev/null +++ b/scripts/docs/.gitignore @@ -0,0 +1,3 @@ +metadata.json +rules.md +venv diff --git a/scripts/docs/Makefile b/scripts/docs/Makefile new file mode 100644 index 00000000..40e4243b --- /dev/null +++ b/scripts/docs/Makefile @@ -0,0 +1,12 @@ + +.PHONY: build +build: metadata.json + python -m venv venv + . venv/bin/activate && pip install -q -r requirements.txt + . venv/bin/activate && python rules_tables.py > rules.md + +metadata.json: + opa eval --format=pretty \ + -d ../../rego/rules \ + -d ../../rego/lib \ + data.rules > metadata.json diff --git a/scripts/docs/requirements.txt b/scripts/docs/requirements.txt new file mode 100644 index 00000000..bf9b7b37 --- /dev/null +++ b/scripts/docs/requirements.txt @@ -0,0 +1 @@ +pytablewriter==0.60.0 diff --git a/scripts/docs/rules_tables.py b/scripts/docs/rules_tables.py new file mode 100644 index 00000000..83fc6e5f --- /dev/null +++ b/scripts/docs/rules_tables.py @@ -0,0 +1,129 @@ +import argparse +from dataclasses import dataclass +import json +import os +from typing import Dict, List, Optional, Tuple +from pytablewriter import MarkdownTableWriter + + +parser = argparse.ArgumentParser( + description="Generate Regula Rules Documentation", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, +) +parser.add_argument( + "--metadata", + default="metadata.json", + help="Rule metadata path", +) + + +@dataclass +class RuleMeta: + """ + Metadata for a single rule + """ + + controls: Optional[Dict[str, Dict[str, List[str]]]] + severity: str + description: str + id: str + title: str + service: Optional[str] + resource_types: List[str] + provider: Optional[str] + input_type: Optional[str] + + +def detect_provider(name: str) -> Tuple[Optional[str], Optional[str]]: + parts = name.split("_", 2) + if len(parts) < 3: + return (None, None) + if parts[0] == "cfn": + return ("aws", "cloudformation") + elif parts[0] == "tf": + return (parts[1], "terraform") + return (None, None) + + +def read_metadata(path: str) -> List[RuleMeta]: + with open(path) as f: + metadata = json.load(f) + rules: List[RuleMeta] = [] + for rule_key, rule in metadata.items(): + metadoc = rule["__rego__metadoc__"] + custom_meta = metadoc.get("custom", {}) + controls = custom_meta.get("controls", {}) + provider, input_type = detect_provider(rule_key) + controls = [ + control_id + for control_ids in controls.values() + for control_id in control_ids + ] + resource_type = rule.get("resource_type") + rules.append( + RuleMeta( + controls=sorted(set(controls)), + severity=custom_meta.get("severity"), + description=metadoc["description"], + id=metadoc["id"], + title=metadoc["title"], + input_type=metadoc.get("input_type") or input_type, + resource_types=[resource_type] if resource_type else [], + service=None, + provider=provider, + ) + ) + return rules + + +def group_rules(rules: List[RuleMeta]) -> Dict[str, List[RuleMeta]]: + by_provider: Dict[str, List[RuleMeta]] = {} + for rule in rules: + provider_rules = by_provider.setdefault(rule.provider, []) + provider_rules.append(rule) + for provider, provider_rules in by_provider.items(): + provider_rules.sort(key=lambda r: r.id) + return by_provider + + +def write_rules_table(header: str, rules: List[RuleMeta]): + writer = MarkdownTableWriter( + table_name=header, + headers=[ + "Summary", + "Resource Types", + "Severity", + "Rule ID", + ], + value_matrix=[ + [ + rule.title, + ", ".join(rule.resource_types) if rule.resource_types else "", + rule.severity, + rule.id, + ] + for rule in rules + ], + ) + writer.write_table() + + +provider_name_map: Dict[str, str] = { + "aws": "AWS", + "google": "Google", + "azurerm": "Azure", +} + + +def main(): + args = parser.parse_args() + rule_metadata = read_metadata(args.metadata) + grouped_rules = group_rules(rule_metadata) + + for provider, provider_rules in grouped_rules.items(): + write_rules_table(provider_name_map[provider], provider_rules) + print() + + +if __name__ == "__main__": + main()