diff --git a/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth.go b/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth.go new file mode 100644 index 000000000..87fed3374 --- /dev/null +++ b/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth.go @@ -0,0 +1,152 @@ +package hashicorpvaultauth + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + + regexp "github.com/wasilibs/go-re2" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +type Scanner struct { + client *http.Client +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) + +var ( + defaultClient = common.SaneHttpClient() + + roleIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"role"}) + `\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b`) + + secretIdPat = regexp.MustCompile(detectors.PrefixRegex([]string{"secret"}) + `\b([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})\b`) + + // Vault URL pattern - HashiCorp Cloud or any HTTPS/HTTP Vault endpoint + vaultUrlPat = regexp.MustCompile(`(https?:\/\/[^\s\/]*\.hashicorp\.cloud(?::\d+)?)(?:\/[^\s]*)?`) +) + +// Keywords are used for efficiently pre-filtering chunks. +func (s Scanner) Keywords() []string { + return []string{"hashicorp"} +} + +// FromData will find and optionally verify HashiCorp Vault AppRole secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + var uniqueRoleIds = make(map[string]struct{}) + for _, match := range roleIdPat.FindAllStringSubmatch(dataStr, -1) { + roleId := strings.TrimSpace(match[1]) + uniqueRoleIds[roleId] = struct{}{} + } + + var uniqueSecretIds = make(map[string]struct{}) + for _, match := range secretIdPat.FindAllStringSubmatch(dataStr, -1) { + secretId := strings.TrimSpace(match[1]) + uniqueSecretIds[secretId] = struct{}{} + } + + var uniqueVaultUrls = make(map[string]struct{}) + for _, match := range vaultUrlPat.FindAllString(dataStr, -1) { + url := strings.TrimSpace(match) + uniqueVaultUrls[url] = struct{}{} + } + + // If no names or secrets found, return empty results + if len(uniqueRoleIds) == 0 || len(uniqueSecretIds) == 0 || len(uniqueVaultUrls) == 0 { + return results, nil + } + + // create combination results that can be verified + for roleId := range uniqueRoleIds { + for secretId := range uniqueSecretIds { + for vaultUrl := range uniqueVaultUrls { + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_HashiCorpVaultAuth, + Raw: []byte(secretId), + RawV2: []byte(fmt.Sprintf("%s:%s", roleId, secretId)), + ExtraData: map[string]string{ + "URL": vaultUrl, + }, + } + + if verify { + client := s.client + if client == nil { + client = defaultClient + } + + isVerified, verificationErr := verifyMatch(ctx, client, roleId, secretId, vaultUrl) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr, roleId, secretId, vaultUrl) + } + results = append(results, s1) + } + } + } + return results, nil +} + +func verifyMatch(ctx context.Context, client *http.Client, roleId, secretId, vaultUrl string) (bool, error) { + payload := map[string]string{ + "role_id": roleId, + "secret_id": secretId, + } + + jsonPayload, err := json.Marshal(payload) + if err != nil { + return false, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, vaultUrl+"/v1/auth/approle/login", bytes.NewBuffer(jsonPayload)) + if err != nil { + return false, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Vault-Namespace", "admin") + + resp, err := client.Do(req) + if err != nil { + return false, err + } + + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + switch resp.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusBadRequest: + body, err := io.ReadAll(resp.Body) + if err != nil { + return false, err + } + + if strings.Contains(string(body), "invalid role or secret ID") { + return false, nil + } else { + return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode) + } + default: + return false, fmt.Errorf("unexpected HTTP response status %d", resp.StatusCode) + } +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_HashiCorpVaultAuth +} + +func (s Scanner) Description() string { + return "HashiCorp Vault AppRole authentication method uses role_id and secret_id for machine-to-machine authentication. These credentials can be used to authenticate with Vault and obtain tokens for accessing secrets." +} diff --git a/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_integration_test.go b/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_integration_test.go new file mode 100644 index 000000000..2b071bbde --- /dev/null +++ b/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_integration_test.go @@ -0,0 +1,181 @@ +//go:build detectors +// +build detectors + +package hashicorpvaultauth + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestHashiCorpVaultAuth_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors6") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + roleId := testSecrets.MustGetField("HASHICORPVAULTAUTH_ROLE_ID") + secretId := testSecrets.MustGetField("HASHICORPVAULTAUTH_SECRET_ID") + inactiveRoleId := testSecrets.MustGetField("HASHICORPVAULTAUTH_ROLE_ID_INACTIVE") + inactiveSecretId := testSecrets.MustGetField("HASHICORPVAULTAUTH_SECRET_ID_INACTIVE") + vaultUrl := testSecrets.MustGetField("HASHICORPVAULTAUTH_URL") + + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + wantVerificationErr bool + }{ + { + name: "found, unverified - complete set with invalid credentials", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("hashicorp config:\nrole_id: %s\nsecret_id: %s\nvault_url: %s", inactiveRoleId, inactiveSecretId, vaultUrl)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_HashiCorpVaultAuth, + Verified: false, + VerificationFromCache: false, + Raw: []byte(inactiveSecretId), + RawV2: []byte(fmt.Sprintf("%s:%s", inactiveRoleId, inactiveSecretId)), + ExtraData: map[string]string{ + "URL": vaultUrl, + }, + StructuredData: nil, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, verified - complete set with valid credentials", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("hashicorp config:\nrole_id: %s\nsecret_id: %s\nvault_url: %s", roleId, secretId, vaultUrl)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_HashiCorpVaultAuth, + Verified: true, + VerificationFromCache: false, + Raw: []byte(secretId), + RawV2: []byte(fmt.Sprintf("%s:%s", roleId, secretId)), + ExtraData: map[string]string{ + "URL": vaultUrl, + }, + StructuredData: nil, + }, + }, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, incomplete set - credentials without vault url", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("vault config:\nrole_id: %s\nsecret_id: %s", roleId, secretId)), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, incomplete set - only role_id", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("vault role_id: %s", roleId)), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "found, incomplete set - only secret_id", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("vault secret_id: %s", secretId)), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + { + name: "not found - no vault context", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + wantVerificationErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("HashiCorpVaultAuth.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + for i := range got { + if len(got[i].Raw) == 0 { + t.Fatalf("no raw secret present: \n %+v", got[i]) + } + if (got[i].VerificationError() != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError()) + } + } + // Fix: Ignore ALL unexported fields using cmpopts.IgnoreUnexported + ignoreOpts := cmpopts.IgnoreUnexported(detectors.Result{}) + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { + t.Errorf("HashiCorpVaultAuth.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_test.go b/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_test.go new file mode 100644 index 000000000..a06869dfc --- /dev/null +++ b/pkg/detectors/hashicorpvaultauth/hashicorpvaultauth_test.go @@ -0,0 +1,163 @@ +package hashicorpvaultauth + +import ( + "context" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick" +) + +var ( + validRoleId = "12345678-1234-1234-1234-123456789abc" // lowercase hex UUID + validSecretId = "87654321-4321-4321-4321-CBA987654321" // mixed case hex UUID + validVaultUrl = "https://my-org.hashicorp.cloud" + invalidRoleId = "12345678-1234-1234-1234-123456789abg" // invalid character 'g' + invalidSecretId = "87654321-4321-4321-4321-CBA98765432G" // invalid character 'G' + keyword = "vault" +) + +func TestHashiCorpVaultAppRoleAuth_Pattern(t *testing.T) { + d := Scanner{} + ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) + tests := []struct { + name string + input string + want []string + }{ + { + name: "valid pattern - complete set (role_id + secret_id + vault_url)", + input: fmt.Sprintf("%s hashicorp:\n role_id = '%s'\nsecret_id = '%s'\nvault_url = '%s'", keyword, validRoleId, validSecretId, validVaultUrl), + want: []string{fmt.Sprintf("%s:%s", validRoleId, validSecretId)}, + }, + { + name: "valid pattern - only role_id (incomplete set)", + input: fmt.Sprintf("%s role_id = '%s'", keyword, validRoleId), + want: []string{}, + }, + { + name: "valid pattern - only secret_id (incomplete set)", + input: fmt.Sprintf("%s secret_id = '%s'", keyword, validSecretId), + want: []string{}, + }, + { + name: "valid pattern - role_id + secret_id but no vault_url (incomplete set)", + input: fmt.Sprintf("%s config:\nrole_id = '%s'\nsecret_id = '%s'", keyword, validRoleId, validSecretId), + want: []string{}, + }, + { + name: "valid pattern - ignore duplicates in complete set", + input: fmt.Sprintf("%s role_id = '%s' | '%s'\nsecret_id = '%s'\nvault_url = '%s'", keyword, validRoleId, validRoleId, validSecretId, validVaultUrl), + want: []string{fmt.Sprintf("%s:%s", validRoleId, validSecretId)}, + }, + { + name: "valid pattern - multiple credentials with vault_url", + input: fmt.Sprintf("%s config:\nrole_id1 = '%s'\nrole_id2 = '%s'\nsecret_id1 = '%s'\nsecret_id2 = '%s'\nvault_url = '%s'", + keyword, validRoleId, + "abcdef12-3456-7890-abcd-ef1234567890", + validSecretId, + "FEDCBA09-8765-4321-FEDC-BA0987654321", + validVaultUrl), + want: []string{ + fmt.Sprintf("%s:%s", validRoleId, validSecretId), + fmt.Sprintf("%s:%s", validRoleId, "FEDCBA09-8765-4321-FEDC-BA0987654321"), + fmt.Sprintf("%s:%s", "abcdef12-3456-7890-abcd-ef1234567890", validSecretId), + fmt.Sprintf("%s:%s", "abcdef12-3456-7890-abcd-ef1234567890", "FEDCBA09-8765-4321-FEDC-BA0987654321"), + }, + }, + { + name: "valid pattern - key out of prefix range", + input: fmt.Sprintf("%s keyword is not close to the real key in the data\n = '%s'", keyword, validRoleId), + want: []string{}, + }, + { + name: "invalid pattern - role_id with invalid character", + input: fmt.Sprintf("%s role_id = '%s'\nvault_url = '%s'", keyword, invalidRoleId, validVaultUrl), + want: []string{}, + }, + { + name: "invalid pattern - secret_id with invalid character", + input: fmt.Sprintf("%s secret_id = '%s'\nvault_url = '%s'", keyword, invalidSecretId, validVaultUrl), + want: []string{}, + }, + { + name: "invalid pattern - role_id too short", + input: fmt.Sprintf("%s role_id = '%s'\nvault_url = '%s'", keyword, "12345678-1234-1234-1234-123456789ab", validVaultUrl), + want: []string{}, + }, + { + name: "invalid pattern - secret_id too long", + input: fmt.Sprintf("%s secret_id = '%s'\nvault_url = '%s'", keyword, "87654321-4321-4321-4321-CBA9876543211", validVaultUrl), + want: []string{}, + }, + { + name: "invalid pattern - role_id with uppercase (should be lowercase only)", + input: fmt.Sprintf("%s role_id = '%s'\nvault_url = '%s'", keyword, "12345678-1234-1234-1234-123456789ABC", validVaultUrl), + want: []string{}, + }, + { + name: "invalid pattern - missing hyphens in UUIDs", + input: fmt.Sprintf("%s role_id = '%s'\nsecret_id = '%s'\nvault_url = '%s'", keyword, "123456781234123412341234567890ab", "87654321432143214321CBA987654321", validVaultUrl), + want: []string{}, + }, + { + name: "valid pattern - alternative service keyword", + input: fmt.Sprintf("hashicorp role_id = '%s'\nsecret_id = '%s'\nvault_url = '%s'", validRoleId, validSecretId, validVaultUrl), + want: []string{fmt.Sprintf("%s:%s", validRoleId, validSecretId)}, + }, + { + name: "valid pattern - vault_url without credentials", + input: fmt.Sprintf("%s vault_url = '%s'", keyword, validVaultUrl), + want: []string{}, + }, + { + name: "invalid pattern - non-hashicorp vault url", + input: fmt.Sprintf("%s role_id = '%s'\nsecret_id = '%s'\nvault_url = 'https://my-vault.company.com'", keyword, validRoleId, validSecretId), + want: []string{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(test.input)) + if len(test.want) > 0 && len(matchedDetectors) == 0 { + t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), test.input) + return + } + + results, err := d.FromData(context.Background(), false, []byte(test.input)) + if err != nil { + t.Errorf("error = %v", err) + return + } + + if len(results) != len(test.want) { + if len(results) == 0 { + t.Errorf("did not receive result") + } else { + t.Errorf("expected %d results, only received %d", len(test.want), len(results)) + } + return + } + + actual := make(map[string]struct{}, len(results)) + for _, r := range results { + if len(r.RawV2) > 0 { + actual[string(r.RawV2)] = struct{}{} + } else { + actual[string(r.Raw)] = struct{}{} + } + } + expected := make(map[string]struct{}, len(test.want)) + for _, v := range test.want { + expected[v] = struct{}{} + } + + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("%s diff: (-want +got)\n%s", test.name, diff) + } + }) + } +} diff --git a/pkg/engine/defaults/defaults.go b/pkg/engine/defaults/defaults.go index 7ab351086..84399d568 100644 --- a/pkg/engine/defaults/defaults.go +++ b/pkg/engine/defaults/defaults.go @@ -349,6 +349,7 @@ import ( "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/happyscribe" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/harness" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/harvest" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hashicorpvaultauth" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hasura" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/hellosign" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors/helpcrunch" @@ -1214,6 +1215,7 @@ func buildDetectorList() []detectors.Detector { &happyscribe.Scanner{}, &harness.Scanner{}, &harvest.Scanner{}, + &hashicorpvaultauth.Scanner{}, &hasura.Scanner{}, &hellosign.Scanner{}, &helpcrunch.Scanner{}, diff --git a/pkg/pb/detectorspb/detectors.pb.go b/pkg/pb/detectorspb/detectors.pb.go index d39878604..6a8a9b92f 100644 --- a/pkg/pb/detectorspb/detectors.pb.go +++ b/pkg/pb/detectorspb/detectors.pb.go @@ -1142,6 +1142,7 @@ const ( DetectorType_WebexBot DetectorType = 1033 DetectorType_TableauPersonalAccessToken DetectorType = 1034 DetectorType_Rootly DetectorType = 1035 + DetectorType_HashiCorpVaultAuth DetectorType = 1036 ) // Enum value maps for DetectorType. @@ -2179,6 +2180,7 @@ var ( 1033: "WebexBot", 1034: "TableauPersonalAccessToken", 1035: "Rootly", + 1036: "HashiCorpVaultAuth", } DetectorType_value = map[string]int32{ "Alibaba": 0, @@ -3213,6 +3215,7 @@ var ( "WebexBot": 1033, "TableauPersonalAccessToken": 1034, "Rootly": 1035, + "HashiCorpVaultAuth": 1036, } ) @@ -3666,7 +3669,7 @@ var file_detectors_proto_rawDesc = []byte{ 0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x53, 0x43, 0x41, 0x50, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45, - 0x10, 0x04, 0x2a, 0xf9, 0x85, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x10, 0x04, 0x2a, 0x92, 0x86, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x10, 0x03, 0x12, @@ -4737,12 +4740,13 @@ var file_detectors_proto_rawDesc = []byte{ 0x88, 0x08, 0x12, 0x0d, 0x0a, 0x08, 0x57, 0x65, 0x62, 0x65, 0x78, 0x42, 0x6f, 0x74, 0x10, 0x89, 0x08, 0x12, 0x1f, 0x0a, 0x1a, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x61, 0x75, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x61, 0x6c, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, - 0x8a, 0x08, 0x12, 0x0b, 0x0a, 0x06, 0x52, 0x6f, 0x6f, 0x74, 0x6c, 0x79, 0x10, 0x8b, 0x08, 0x42, - 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, - 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, - 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, - 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x8a, 0x08, 0x12, 0x0b, 0x0a, 0x06, 0x52, 0x6f, 0x6f, 0x74, 0x6c, 0x79, 0x10, 0x8b, 0x08, 0x12, + 0x17, 0x0a, 0x12, 0x48, 0x61, 0x73, 0x68, 0x69, 0x43, 0x6f, 0x72, 0x70, 0x56, 0x61, 0x75, 0x6c, + 0x74, 0x41, 0x75, 0x74, 0x68, 0x10, 0x8c, 0x08, 0x42, 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, + 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, + 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, + 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/detectors.proto b/proto/detectors.proto index 32b9fb73a..5e3a4066e 100644 --- a/proto/detectors.proto +++ b/proto/detectors.proto @@ -1045,6 +1045,7 @@ enum DetectorType { WebexBot = 1033; TableauPersonalAccessToken = 1034; Rootly = 1035; + HashiCorpVaultAuth = 1036; } message Result {