diff --git a/assets/queries/common/passwords_and_secrets/regex_rules.json b/assets/queries/common/passwords_and_secrets/regex_rules.json index 4620f0285cd..a0046c4df1e 100644 --- a/assets/queries/common/passwords_and_secrets/regex_rules.json +++ b/assets/queries/common/passwords_and_secrets/regex_rules.json @@ -40,6 +40,10 @@ { "description": "Allow password retrieved from ARM parameters", "regex": "(?i)['\"]?password['\"]?\\s*[:=]\\s*['\"]?\\s*,\\s*parameters\\(['\"]([a-zA-Z][a-zA-Z0-9_-]*)['\"]['\"]?\\)" + }, + { + "description": "Avoiding Proto File fields", + "regex": "(?i)password\\s*=\\s*[1-9][0-9]{0,8}\\s*;" } ], "specialMask": "(?i)['\"]?password['\"]?\\s*[:=]\\s*" @@ -263,7 +267,7 @@ { "id": "7f370dd5-eea3-4e5f-8354-3cb2506f9f13", "name": "Generic Access Key", - "regex": "(?i)^\\s*['\"]?(access)[_]?key['\"]?\\s*[:=]\\s*['\"]?([[A-Za-z0-9\/~^_!@&%()=?*+-]+)['\"]?", + "regex": "(?i)^\\s*['\"]?access[_]?key['\"]?\\s*[:=]\\s*['\"]?([[A-Za-z0-9\/~^_!@&%()=?*+-]+)['\"]?", "specialMask": "(?i)['\"]?access[_]?key['\"]?\\s*[:=]\\s*" }, { @@ -279,6 +283,10 @@ { "description": "Avoid Docker Compose secrets paths", "regex": "(?i)['\"]?private[_]?key['\"]?\\s*[:=]\\s*['\"]?/run/secrets/\\w+['\"]?" + }, + { + "description": "Avoiding Proto File fields", + "regex": "(?i)private[_]?key\\s*=\\s*[1-9][0-9]{0,8}\\s*;" } ] }, @@ -313,7 +321,7 @@ }, { "description": "Avoiding TF resource access", - "regex": "(?i)['\"]?token(_)?(key)?['\"]?\\s*=\\s*([a-zA-z_]+(.))?[a-zA-z_]+(.)[a-zA-z_]+(.)[a-zA-z_]+" + "regex": "(?i)['\"]?token(_)?(key)?['\"]?\\s*[:=]\\s*(([a-zA-Z_]+(\\[([a-zA-Z_]+\\.[a-zA-Z_]+.*|\\d+)\\])?\\.[a-zA-Z_]+(\\[([a-zA-Z_]+\\.[a-zA-Z_]+.*|\\d+)\\])?)\\s*([\\[\\.\\)\\]\\}\\$]|(:\\s*null))|null)" }, { "description": "Avoiding TF creation token", @@ -339,10 +347,6 @@ "description": "Avoiding next_token Var", "regex": "(?i)['\"]?next(_)?token['\"]?\\s*[:=]\\s*['\"]?([[A-Za-z0-9/~^_!@&%()=?*+-]+)['\"]?" }, - { - "description": "Avoiding next_token Var", - "regex": "(?i)['\"]?next(_)?token['\"]?\\s*[:=]\\s*['\"]?([[A-Za-z0-9/~^_!@&%()=?*+-]+)['\"]?" - }, { "description": "Avoiding references to module outputs in Bicep", "regex": "(?i)token(_)?(key)?\\s*[:=]\\s*([a-zA-Z][a-zA-Z0-9_]*)\\.outputs\\.([a-zA-Z][a-zA-Z0-9_]*)" @@ -350,13 +354,17 @@ { "description": "Avoiding Run After Triggers (if written in conformity with best practices)", "regex": "(?i)['\"](HTTP|Parse_JSON|Try|Catch)_-_(Get_)?OAuth_Token['\"]?\\s*[:=]\\s*['\"]?([[A-Za-z0-9/~^_!@&%()=?*+-]+)['\"]?" + }, + { + "description": "Avoiding Proto File fields", + "regex": "(?i)token(_)?(key)?\\s*=\\s*[1-9][0-9]{0,8}\\s*;" } ], "specialMask": "(?i)['\"]?token(_)?(key)?['\"]?\\s*[:=]\\s*" }, { - "id": "e0f01838-b1c2-4669-b84b-981949ebe5ed", + "id": "0ec60fb3-5b78-4da0-bea0-b854fd9bf8b9", "name": "CloudFormation Secret Template", "regex": "(?i)['\"]?SecretStringTemplate['\"]?\\s*:\\s*['\"]?{([\\\":A-Za-z0-9/~^_!@&%()=?*+-]{10,})}", "specialMask": "(?i)['\"]?SecretStringTemplate['\"]?\\s*:\\s*" @@ -364,15 +372,19 @@ { "id": "9fb1cd65-7a07-4531-9bcf-47589d0f82d6", "name": "Encryption Key", - "regex": "(?i)['\"]?encryption[_]?key['\"]?\\s*[:=]\\s*['\"]?([[A-Za-z0-9/~^_!@&%()=?*+-]+)['\"]?", + "regex": "(?i)['\"]?encryption[_]?key['\"]?\\s*[:=]\\s*['\"]?[A-Za-z0-9/~^_!@&%()=?*+-]+['\"]?", "allowRules": [ { "description": "Avoiding TF resource access", - "regex": "(?i)['\"]?encryption[_]?key['\"]?\\s*=\\s*([a-zA-z_]+(.))?[a-zA-z_]+(.)[a-zA-z_]+(.)[a-zA-z_]+" + "regex": "(?i)['\"]?encryption[_]?key['\"]?\\s*[:=]\\s*(([a-zA-Z_]+(\\[([a-zA-Z_]+\\.[a-zA-Z_]+.*|\\d+)\\])?\\.[a-zA-Z_]+(\\[([a-zA-Z_]+\\.[a-zA-Z_]+.*|\\d+)\\])?)\\s*([\\[\\.\\)\\]\\}\\$]|(:\\s*null))|null)" }, { "description": "Avoiding CLoudformation KeyName", "regex": "['\"]?EncryptionKey['\"]?\\s*[:=]\\s*['\"]?([A-Za-z0-9/~^_!@&%()=?*+-.]+)['\"]?" + }, + { + "description": "Avoiding Proto File fields", + "regex": "(?i)encryption[_]?key\\s*=\\s*[1-9][0-9]{0,8}\\s*;" } ], "specialMask": "(?i)['\"]?encryption[_]?key['\"]?\\s*[:=]\\s*" diff --git a/assets/queries/common/passwords_and_secrets/test/negative28.tf b/assets/queries/common/passwords_and_secrets/test/negative28.tf index 7239fcb2612..7843cd6f5e9 100644 --- a/assets/queries/common/passwords_and_secrets/test/negative28.tf +++ b/assets/queries/common/passwords_and_secrets/test/negative28.tf @@ -1,4 +1,112 @@ -provider rancher2 { - api_url = data.terraform_remote_state.rancher.outputs.api_url - token_key = data.terraform_remote_state.rancher.outputs.token_key +# Sample to test 'Generic Token' - allow TF resource access rule +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } } + +provider "aws" { + region = "us-east-1" +} + +variable "auth_token" { + description = "Authentication token" + type = string + sensitive = true +} + +variable "environment" { + description = "Deployment environment" + type = string + default = "production" +} + +variable "enabled" { + description = "Whether to enable resources" + type = bool + default = true +} + +variable "clients" { + description = "Client configurations" + type = object({ + oauth = map(object({ + enabled = bool + })) + }) +} + +resource "aws_secretsmanager_secret_version" "token_version" { + for_each = { for k, v in var.clients.oauth : k => v if var.enabled } + + secret_id = aws_secretsmanager_secret.client_token_secret[each.key].id + secret_string = jsonencode({ "client" : each.key, "token" : random_password.client_token[each.key].result }) +} + +resource "aws_secretsmanager_secret_version" "token_version_2" { + for_each = { for k, v in var.clients.oauth : k => v if var.enabled } + + secret_id = aws_secretsmanager_secret.client_token_secret[each.key].id + secret_string = jsonencode({ "client" : each.key, "token" : random_password[each.key].client_token.result }) +} + +resource "aws_secretsmanager_secret_version" "token_version_3" { + for_each = { for k, v in var.clients.oauth : k => v if var.enabled } + + secret_id = aws_secretsmanager_secret.client_token_secret[each.key].id + secret_string = jsonencode({ "client" : each.key, "token" : random_password["index"].client_token.result }) +} + +resource "aws_lb_listener" "https" { + count = var.enabled ? 1 : 0 + load_balancer_arn = aws_lb.main[0].arn + port = 443 + protocol = "HTTPS" + + default_action { + type = "forward" + target_group_arn = [for t in aws_lb_target_group.app : t.token_key] + } +} + +resource "aws_lb_listener" "https_null" { + count = var.enabled ? 1 : 0 + load_balancer_arn = aws_lb.main[0].arn + port = 443 + protocol = "HTTPS" + + default_action { + type = "fixed-response" + token_key = null + } +} +module "auth_service" { + source = "./modules/auth" + + token = var.auth_token +} +module "api_gateway" { + source = "./modules/gateway" + + token = module.auth_service.token_output.value +} +module "legacy_service" { + source = "./modules/legacy" + + token = data.aws_secretsmanager_secret_version.existing_token.secret_string +} + +locals { + token_config = { + value = aws_secretsmanager_secret.client_token_secret["primary"].arn + } +} + +module "monitoring" { + source = "./modules/monitoring" + + token = local.token_config.value +} \ No newline at end of file diff --git a/assets/queries/common/passwords_and_secrets/test/negative60.proto b/assets/queries/common/passwords_and_secrets/test/negative60.proto new file mode 100644 index 00000000000..63d53613600 --- /dev/null +++ b/assets/queries/common/passwords_and_secrets/test/negative60.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +// Contains "secret", "key", "password" and "token" keywords - would flag if not for allow rules +// Note - This is as valid proto file that generates output with "protoc" command as of version "libprotoc 34.0" + +package com.example.security_test.v1; + +import "google/protobuf/wrappers.proto"; + +message SampleMessageNegative { + google.protobuf.StringValue refresh_token = 536870911; // if value is larger - out of range error "Field numbers cannot be greater than 536870911." - Generic Token + google.protobuf.StringValue access_token= 111111111; // Generic Token + google.protobuf.StringValue id_token = 3; // Generic Token + google.protobuf.StringValue bearer_toaken = 4;;; // Generic Token + google.protobuf.StringValue api_token = 7 ; // Generic Token + google.protobuf.StringValue token = 8; // Generic Token + google.protobuf.StringValue aws_session_token = 9; // Generic Token + google.protobuf.StringValue sas_token = 12; // Generic Token + google.protobuf.StringValue auth_token = 13; // Generic Token + google.protobuf.StringValue bot_token = 14; // Generic Token + google.protobuf.StringValue callback_token = 32; // Generic Token + google.protobuf.StringValue k8s_service_account_token = 33; // Generic Token + google.protobuf.StringValue registry_token = 34; // Generic Token + google.protobuf.StringValue deploy_token = 35; // Generic Token + google.protobuf.StringValue ci_token = 36; // Generic Token + google.protobuf.StringValue npm_token = 37; // Generic Token + google.protobuf.StringValue pypi_token = 38; // Generic Token + google.protobuf.StringValue app_installation_token = 20; // Generic Token + google.protobuf.StringValue twilio_auth_token = 21; // Generic Token + google.protobuf.StringValue twilio_auth_token_key = 121; // Generic Token + google.protobuf.StringValue test_token_ = 122 ; // Generic Token + google.protobuf.StringValue sonar_token = 39;google.protobuf.StringValue codecov_token = 40;// trailing comment test - Generic Token + google.protobuf.StringValue jwt_private_key = 25; // Generic Private Key + google.protobuf.StringValue ssh_private_key = 26; // Generic Private Key + google.protobuf.StringValue tls_private_key = 27; // Generic Private Key + google.protobuf.StringValue ca_private_key = 28 ; // Generic Private Key + google.protobuf.StringValue cosign_private_key = 41; // Generic Private Key + google.protobuf.StringValue service_account_private_key = 30; // Generic Private Key + google.protobuf.StringValue app_private_key = 19; // Generic Private Key + google.protobuf.StringValue gcp_private_key = 10; // Generic Private Key + google.protobuf.StringValue private_key = 5; // Generic Private Key + google.protobuf.StringValue sp_private_key = 6; // Generic Private Key + google.protobuf.StringValue encryption_key = 22; // Encryption Key + google.protobuf.StringValue data_encryption_key= 23 ; // Encryption Key + google.protobuf.StringValue key_encryption_key=24; // Encryption Key + google.protobuf.StringValue registry_password = 104; // Generic Password + google.protobuf.StringValue artifactory_password = 107 ; // Generic Password + google.protobuf.StringValue nexus_password = 108; // Generic Password + string password = 64; // Generic Password +} \ No newline at end of file diff --git a/assets/queries/common/passwords_and_secrets/test/negative61.tf b/assets/queries/common/passwords_and_secrets/test/negative61.tf new file mode 100644 index 00000000000..9b986baab96 --- /dev/null +++ b/assets/queries/common/passwords_and_secrets/test/negative61.tf @@ -0,0 +1,134 @@ +# Sample for 'Encryption Key' - avoiding TF resource access rule +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +variable "encryption_key" { + description = "Encryption key from external config" + type = string + sensitive = true +} + +variable "environment" { + description = "Deployment environment" + type = string + default = "production" +} + +variable "enabled" { + description = "Whether to enable resources" + type = bool + default = true +} + +variable "clients" { + description = "Client configurations" + type = object({ + storage = map(object({ + enabled = bool + })) + }) +} + +resource "aws_kms_key" "client_encryption_key" { + for_each = { for k, v in var.clients.storage : k => v if var.enabled } + + description = "KMS key for ${each.key}" + deletion_window_in_days = 30 + enable_key_rotation = true +} + +resource "aws_kms_alias" "client_encryption_alias" { + for_each = { for k, v in var.clients.storage : k => v if var.enabled } + + name = "alias/${each.key}-encryption" + target_key_id = aws_kms_key.client_encryption_key[each.key].key_id +} + +module "storage" { + for_each = { for k, v in var.clients.storage : k => v if var.enabled } + source = "./modules/storage" + + encryption_key = aws_kms_key.client_encryption_key[each.key].arn +} + +module "storage_2" { + for_each = { for k, v in var.clients.storage : k => v if var.enabled } + source = "./modules/storage" + + encryption_key = aws_kms_key[each.key].client_encryption_key.arn +} + +module "storage_3" { + for_each = { for k, v in var.clients.storage : k => v if var.enabled } + source = "./modules/storage" + + encryption_key = aws_kms_key["index"].client_encryption_key.arn +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "bucket_enc" { + count = var.enabled ? 1 : 0 + bucket = aws_s3_bucket.main[0].id + + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "aws:kms" + encryption_key = [for k in aws_kms_key.client_encryption_key : k.arn] + } + } +} + +module "optional_encryption" { + source = "./modules/storage" + + encryption_key = null +} + +module "database" { + source = "./modules/database" + + encryption_key = var.encryption_key +} + +module "encryption" { + source = "./modules/encryption" + + environment = var.environment +} + +module "app" { + source = "./modules/app" + + encryption_key = module.encryption.key_output.value +} + +data "aws_kms_key" "existing" { + key_id = "alias/existing-encryption-key" +} + +module "legacy" { + source = "./modules/legacy" + + encryption_key = data.aws_kms_key.existing.arn +} + +locals { + encryption_config = { + key_arn = aws_kms_key.client_encryption_key["primary"].arn + } +} + +module "monitoring" { + source = "./modules/monitoring" + + encryption_key = local.encryption_config.key_arn +} \ No newline at end of file diff --git a/assets/queries/common/passwords_and_secrets/test/positive56.proto b/assets/queries/common/passwords_and_secrets/test/positive56.proto new file mode 100644 index 00000000000..4367e728812 --- /dev/null +++ b/assets/queries/common/passwords_and_secrets/test/positive56.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +// This sample should not flag the message defined only the exposed secrets in comments : +// "password" = "test_sample" +// "secret_key" : minimum_ten_characters +// "unsafe_token" : "is_this_safe" +// Note - This is as valid proto file that generates output with "protoc" command as of version "libprotoc 34.0" + +package com.example.security_test.v1; + +import "google/protobuf/wrappers.proto"; + +message InocentMessage { + google.protobuf.StringValue safe_value = 1; + double not_a_password = 2; + float not_a_token = 3; + string not_an_encryption_key = 4; +} \ No newline at end of file diff --git a/assets/queries/common/passwords_and_secrets/test/positive57.yaml b/assets/queries/common/passwords_and_secrets/test/positive57.yaml new file mode 100644 index 00000000000..c0f56b93551 --- /dev/null +++ b/assets/queries/common/passwords_and_secrets/test/positive57.yaml @@ -0,0 +1,8 @@ +# Samples for "Encryption Key" query +- hosts: all + remote_user: root + vars: + encryption_key: aes256_my_super_secret_key_value + encryption_key: Ab3f8G2kL9mN4pQ7 + encryptionkey: 0123456789abcdef + diff --git a/assets/queries/common/passwords_and_secrets/test/positive_expected_result.json b/assets/queries/common/passwords_and_secrets/test/positive_expected_result.json index e29ad869f20..88cc664b56b 100644 --- a/assets/queries/common/passwords_and_secrets/test/positive_expected_result.json +++ b/assets/queries/common/passwords_and_secrets/test/positive_expected_result.json @@ -460,5 +460,41 @@ "severity": "HIGH", "line": 4, "fileName": "positive55.json" + }, + { + "queryName": "Passwords And Secrets - Generic Password", + "severity": "HIGH", + "line": 4, + "fileName": "positive56.proto" + }, + { + "queryName": "Passwords And Secrets - Generic Secret", + "severity": "HIGH", + "line": 5, + "fileName": "positive56.proto" + }, + { + "queryName": "Passwords And Secrets - Generic Token", + "severity": "HIGH", + "line": 6, + "fileName": "positive56.proto" + }, + { + "queryName": "Passwords And Secrets - Encryption Key", + "severity": "HIGH", + "line": 5, + "fileName": "positive57.yaml" + }, + { + "queryName": "Passwords And Secrets - Encryption Key", + "severity": "HIGH", + "line": 6, + "fileName": "positive57.yaml" + }, + { + "queryName": "Passwords And Secrets - Encryption Key", + "severity": "HIGH", + "line": 7, + "fileName": "positive57.yaml" } ] \ No newline at end of file diff --git a/pkg/engine/secrets/inspector_test.go b/pkg/engine/secrets/inspector_test.go index 2b5bbf12b55..f642d0e1772 100644 --- a/pkg/engine/secrets/inspector_test.go +++ b/pkg/engine/secrets/inspector_test.go @@ -260,6 +260,34 @@ var OriginalData7 = `# kics-scan disable=baee238e-1921-4801-9c3f-79ae1d7b2cbc register: result ` +var OriginalData8 = ` + syntax = "proto3"; + + package com.example.security_test.v1; + + import "google/protobuf/wrappers.proto"; + + message ResultsThatFlag { + google.protobuf.StringValue refresh_token = 536870911; // if value is larger - out of range error "Field numbers cannot be greater than 536870911." - Generic Token + google.protobuf.StringValue access_token= 1; // Generic Token + google.protobuf.StringValue id_token = 3; // Generic Token + google.protobuf.StringValue bearer_toaken = 4;;; // Generic Token + google.protobuf.StringValue api_token = 7 ; // Generic Token + google.protobuf.StringValue token = 8; // Generic Token + google.protobuf.StringValue sonar_token = 39;google.protobuf.StringValue codecov_token = 40;// trailing comment test - Generic Token + google.protobuf.StringValue jwt_private_key = 25; // Generic Private Key + google.protobuf.StringValue ssh_private_key = 26; // Generic Private Key + google.protobuf.StringValue tls_private_key = 27; // Generic Private Key + google.protobuf.StringValue sp_private_key = 6; // Generic Private Key + google.protobuf.StringValue encryption_key = 22; // Encryption Key + google.protobuf.StringValue data_encryption_key= 23 ; // Encryption Key + google.protobuf.StringValue key_encryption_key=24; // Encryption Key + google.protobuf.StringValue registry_password = 104; // Generic Password + google.protobuf.StringValue artifactory_password = 107 ; // Generic Password + google.protobuf.StringValue nexus_password = 108; // Generic Password + string password = 64; // Generic Password +` + var testInspectInput = []struct { name string files model.FileMetadatas @@ -418,6 +446,21 @@ var testInspectInput = []struct { wantVuln: []model.Vulnerability{}, wantErr: false, }, + { + name: "valid_no_results", + files: model.FileMetadatas{ + { + ID: "a6fbadc6-da29-4340-8d56-aa26a8852526", + Document: model.Document{}, + OriginalData: OriginalData8, + LinesOriginalData: utils.SplitLines(OriginalData8), + Kind: "PROTO", + FilePath: "assets/queries/common/passwords_and_secrets/test/negative60.proto", + }, + }, + wantVuln: []model.Vulnerability{}, + wantErr: false, + }, } var testNewInspectorInputs = []struct {