[Feat] Planetscale Analyzer (#3928)
* implemented planetscale anlayzer * linked detector with analyzer for planetscale. override input form for TUI. refactor some variable name for clarrification. * clean up scopes for planetscale. * add database and other scopes * add remaining scopes and refactoring * updated the analyzer type value in expected output of notion test. * updated the expected output for analyzers updated the go-pretty library in planetscale. added extra information from results to be ignore like AnalysisInfo, ExtraData etc. * updated expected output of test.
This commit is contained in:
@@ -87,6 +87,7 @@ const (
|
||||
AnalyzerTypeNotion
|
||||
AnalyzerTypeAirtable
|
||||
AnalyzerTypeDigitalOcean
|
||||
AnalyzerTypePlanetScale
|
||||
// Add new items here with AnalyzerType prefix
|
||||
)
|
||||
|
||||
@@ -120,6 +121,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{
|
||||
AnalyzerTypeTwilio: "Twilio",
|
||||
AnalyzerTypePrivateKey: "PrivateKey",
|
||||
AnalyzerTypeNotion: "Notion",
|
||||
AnalyzerTypePlanetScale: "PlanetScale",
|
||||
// Add new mappings here
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,216 @@
|
||||
// Code generated by go generate; DO NOT EDIT.
|
||||
package planetscale
|
||||
|
||||
import "errors"
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
Invalid Permission = iota
|
||||
ReadOrganization Permission = iota
|
||||
ReadInvoices Permission = iota
|
||||
ReadDatabases Permission = iota
|
||||
ReadAuditLogs Permission = iota
|
||||
CreateDatabases Permission = iota
|
||||
DeleteDatabases Permission = iota
|
||||
ReadOauthApplications Permission = iota
|
||||
WriteOauthTokens Permission = iota
|
||||
ReadOauthTokens Permission = iota
|
||||
DeleteOauthTokens Permission = iota
|
||||
ReadDatabase Permission = iota
|
||||
WriteDatabase Permission = iota
|
||||
DeleteDatabase Permission = iota
|
||||
ReadBranch Permission = iota
|
||||
CreateBranch Permission = iota
|
||||
DeleteBranch Permission = iota
|
||||
DeleteBranchPassword Permission = iota
|
||||
DeleteProductionBranch Permission = iota
|
||||
DeleteProductionBranchPassword Permission = iota
|
||||
ReadDeployRequest Permission = iota
|
||||
CreateDeployRequest Permission = iota
|
||||
ApproveDeployRequest Permission = iota
|
||||
ConnectBranch Permission = iota
|
||||
ConnectProductionBranch Permission = iota
|
||||
ReadComment Permission = iota
|
||||
CreateComment Permission = iota
|
||||
RestoreBackup Permission = iota
|
||||
WriteBackups Permission = iota
|
||||
ReadBackups Permission = iota
|
||||
DeleteBackups Permission = iota
|
||||
RestoreProductionBranchBackup Permission = iota
|
||||
DeleteProductionBranchBackups Permission = iota
|
||||
)
|
||||
|
||||
var (
|
||||
PermissionStrings = map[Permission]string{
|
||||
ReadOrganization: "read_organization",
|
||||
ReadInvoices: "read_invoices",
|
||||
ReadDatabases: "read_databases",
|
||||
ReadAuditLogs: "read_audit_logs",
|
||||
CreateDatabases: "create_databases",
|
||||
DeleteDatabases: "delete_databases",
|
||||
ReadOauthApplications: "read_oauth_applications",
|
||||
WriteOauthTokens: "write_oauth_tokens",
|
||||
ReadOauthTokens: "read_oauth_tokens",
|
||||
DeleteOauthTokens: "delete_oauth_tokens",
|
||||
ReadDatabase: "read_database",
|
||||
WriteDatabase: "write_database",
|
||||
DeleteDatabase: "delete_database",
|
||||
ReadBranch: "read_branch",
|
||||
CreateBranch: "create_branch",
|
||||
DeleteBranch: "delete_branch",
|
||||
DeleteBranchPassword: "delete_branch_password",
|
||||
DeleteProductionBranch: "delete_production_branch",
|
||||
DeleteProductionBranchPassword: "delete_production_branch_password",
|
||||
ReadDeployRequest: "read_deploy_request",
|
||||
CreateDeployRequest: "create_deploy_request",
|
||||
ApproveDeployRequest: "approve_deploy_request",
|
||||
ConnectBranch: "connect_branch",
|
||||
ConnectProductionBranch: "connect_production_branch",
|
||||
ReadComment: "read_comment",
|
||||
CreateComment: "create_comment",
|
||||
RestoreBackup: "restore_backup",
|
||||
WriteBackups: "write_backups",
|
||||
ReadBackups: "read_backups",
|
||||
DeleteBackups: "delete_backups",
|
||||
RestoreProductionBranchBackup: "restore_production_branch_backup",
|
||||
DeleteProductionBranchBackups: "delete_production_branch_backups",
|
||||
}
|
||||
|
||||
StringToPermission = map[string]Permission{
|
||||
"read_organization": ReadOrganization,
|
||||
"read_invoices": ReadInvoices,
|
||||
"read_databases": ReadDatabases,
|
||||
"read_audit_logs": ReadAuditLogs,
|
||||
"create_databases": CreateDatabases,
|
||||
"delete_databases": DeleteDatabases,
|
||||
"read_oauth_applications": ReadOauthApplications,
|
||||
"write_oauth_tokens": WriteOauthTokens,
|
||||
"read_oauth_tokens": ReadOauthTokens,
|
||||
"delete_oauth_tokens": DeleteOauthTokens,
|
||||
"read_database": ReadDatabase,
|
||||
"write_database": WriteDatabase,
|
||||
"delete_database": DeleteDatabase,
|
||||
"read_branch": ReadBranch,
|
||||
"create_branch": CreateBranch,
|
||||
"delete_branch": DeleteBranch,
|
||||
"delete_branch_password": DeleteBranchPassword,
|
||||
"delete_production_branch": DeleteProductionBranch,
|
||||
"delete_production_branch_password": DeleteProductionBranchPassword,
|
||||
"read_deploy_request": ReadDeployRequest,
|
||||
"create_deploy_request": CreateDeployRequest,
|
||||
"approve_deploy_request": ApproveDeployRequest,
|
||||
"connect_branch": ConnectBranch,
|
||||
"connect_production_branch": ConnectProductionBranch,
|
||||
"read_comment": ReadComment,
|
||||
"create_comment": CreateComment,
|
||||
"restore_backup": RestoreBackup,
|
||||
"write_backups": WriteBackups,
|
||||
"read_backups": ReadBackups,
|
||||
"delete_backups": DeleteBackups,
|
||||
"restore_production_branch_backup": RestoreProductionBranchBackup,
|
||||
"delete_production_branch_backups": DeleteProductionBranchBackups,
|
||||
}
|
||||
|
||||
PermissionIDs = map[Permission]int{
|
||||
ReadOrganization: 1,
|
||||
ReadInvoices: 2,
|
||||
ReadDatabases: 3,
|
||||
ReadAuditLogs: 4,
|
||||
CreateDatabases: 5,
|
||||
DeleteDatabases: 6,
|
||||
ReadOauthApplications: 7,
|
||||
WriteOauthTokens: 8,
|
||||
ReadOauthTokens: 9,
|
||||
DeleteOauthTokens: 10,
|
||||
ReadDatabase: 11,
|
||||
WriteDatabase: 12,
|
||||
DeleteDatabase: 13,
|
||||
ReadBranch: 14,
|
||||
CreateBranch: 15,
|
||||
DeleteBranch: 16,
|
||||
DeleteBranchPassword: 17,
|
||||
DeleteProductionBranch: 18,
|
||||
DeleteProductionBranchPassword: 19,
|
||||
ReadDeployRequest: 20,
|
||||
CreateDeployRequest: 21,
|
||||
ApproveDeployRequest: 22,
|
||||
ConnectBranch: 23,
|
||||
ConnectProductionBranch: 24,
|
||||
ReadComment: 25,
|
||||
CreateComment: 26,
|
||||
RestoreBackup: 27,
|
||||
WriteBackups: 28,
|
||||
ReadBackups: 29,
|
||||
DeleteBackups: 30,
|
||||
RestoreProductionBranchBackup: 31,
|
||||
DeleteProductionBranchBackups: 32,
|
||||
}
|
||||
|
||||
IdToPermission = map[int]Permission{
|
||||
1: ReadOrganization,
|
||||
2: ReadInvoices,
|
||||
3: ReadDatabases,
|
||||
4: ReadAuditLogs,
|
||||
5: CreateDatabases,
|
||||
6: DeleteDatabases,
|
||||
7: ReadOauthApplications,
|
||||
8: WriteOauthTokens,
|
||||
9: ReadOauthTokens,
|
||||
10: DeleteOauthTokens,
|
||||
11: ReadDatabase,
|
||||
12: WriteDatabase,
|
||||
13: DeleteDatabase,
|
||||
14: ReadBranch,
|
||||
15: CreateBranch,
|
||||
16: DeleteBranch,
|
||||
17: DeleteBranchPassword,
|
||||
18: DeleteProductionBranch,
|
||||
19: DeleteProductionBranchPassword,
|
||||
20: ReadDeployRequest,
|
||||
21: CreateDeployRequest,
|
||||
22: ApproveDeployRequest,
|
||||
23: ConnectBranch,
|
||||
24: ConnectProductionBranch,
|
||||
25: ReadComment,
|
||||
26: CreateComment,
|
||||
27: RestoreBackup,
|
||||
28: WriteBackups,
|
||||
29: ReadBackups,
|
||||
30: DeleteBackups,
|
||||
31: RestoreProductionBranchBackup,
|
||||
32: DeleteProductionBranchBackups,
|
||||
}
|
||||
)
|
||||
|
||||
// ToString converts a Permission enum to its string representation
|
||||
func (p Permission) ToString() (string, error) {
|
||||
if str, ok := PermissionStrings[p]; ok {
|
||||
return str, nil
|
||||
}
|
||||
return "", errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// ToID converts a Permission enum to its ID
|
||||
func (p Permission) ToID() (int, error) {
|
||||
if id, ok := PermissionIDs[p]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission")
|
||||
}
|
||||
|
||||
// PermissionFromString converts a string representation to its Permission enum
|
||||
func PermissionFromString(s string) (Permission, error) {
|
||||
if p, ok := StringToPermission[s]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission string")
|
||||
}
|
||||
|
||||
// PermissionFromID converts an ID to its Permission enum
|
||||
func PermissionFromID(id int) (Permission, error) {
|
||||
if p, ok := IdToPermission[id]; ok {
|
||||
return p, nil
|
||||
}
|
||||
return 0, errors.New("invalid permission ID")
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
permissions:
|
||||
- read_organization
|
||||
- read_invoices
|
||||
- read_databases
|
||||
- read_audit_logs
|
||||
- create_databases
|
||||
- delete_databases
|
||||
- read_oauth_applications
|
||||
- write_oauth_tokens
|
||||
- read_oauth_tokens
|
||||
- delete_oauth_tokens
|
||||
- read_database
|
||||
- write_database
|
||||
- delete_database
|
||||
- read_branch
|
||||
- create_branch
|
||||
- delete_branch
|
||||
- delete_branch_password
|
||||
- delete_production_branch
|
||||
- delete_production_branch_password
|
||||
- read_deploy_request
|
||||
- create_deploy_request
|
||||
- approve_deploy_request
|
||||
- connect_branch
|
||||
- connect_production_branch
|
||||
- read_comment
|
||||
- create_comment
|
||||
- restore_backup
|
||||
- write_backups
|
||||
- read_backups
|
||||
- delete_backups
|
||||
- restore_production_branch_backup
|
||||
- delete_production_branch_backups
|
||||
@@ -0,0 +1,598 @@
|
||||
//go:generate generate_permissions permissions.yaml permissions.go planetscale
|
||||
|
||||
package planetscale
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jedib0t/go-pretty/v6/table"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
var _ analyzers.Analyzer = (*Analyzer)(nil)
|
||||
|
||||
type Analyzer struct {
|
||||
Cfg *config.Config
|
||||
}
|
||||
|
||||
func (Analyzer) Type() analyzers.AnalyzerType { return analyzers.AnalyzerTypePlanetScale }
|
||||
|
||||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
|
||||
id, ok := credInfo["id"]
|
||||
if !ok {
|
||||
return nil, errors.New("missing id in credInfo")
|
||||
}
|
||||
key, ok := credInfo["token"]
|
||||
if !ok {
|
||||
return nil, errors.New("missing key in credInfo")
|
||||
}
|
||||
info, err := AnalyzePermissions(a.Cfg, id, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return secretInfoToAnalyzerResult(info), nil
|
||||
}
|
||||
|
||||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
|
||||
if info == nil {
|
||||
return nil
|
||||
}
|
||||
result := analyzers.AnalyzerResult{
|
||||
AnalyzerType: analyzers.AnalyzerTypePlanetScale,
|
||||
Metadata: nil,
|
||||
Bindings: make([]analyzers.Binding, 0),
|
||||
}
|
||||
|
||||
resource := analyzers.Resource{
|
||||
Name: info.Organization.Name,
|
||||
FullyQualifiedName: "planetscale.com/organization/" + info.Organization.Id,
|
||||
Type: "Organization",
|
||||
}
|
||||
|
||||
for _, permission := range info.OrgPermissions {
|
||||
result.Bindings = append(result.Bindings, analyzers.Binding{
|
||||
Resource: resource,
|
||||
Permission: analyzers.Permission{
|
||||
Value: permission,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
for db, permissions := range info.DBPermissions {
|
||||
dbResource := analyzers.Resource{
|
||||
Name: db.Name,
|
||||
FullyQualifiedName: "planetscale.com/database/" + db.Id,
|
||||
Type: "Database",
|
||||
Parent: &resource,
|
||||
}
|
||||
for _, permission := range permissions {
|
||||
result.Bindings = append(result.Bindings, analyzers.Binding{
|
||||
Resource: dbResource,
|
||||
Permission: analyzers.Permission{
|
||||
Value: permission,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return &result
|
||||
}
|
||||
|
||||
//go:embed scopes.json
|
||||
var scopesConfig []byte
|
||||
|
||||
type HttpStatusTest struct {
|
||||
Endpoint string `json:"endpoint"`
|
||||
Method string `json:"method"`
|
||||
Payload interface{} `json:"payload"`
|
||||
ValidStatuses []int `json:"valid_status_code"`
|
||||
InvalidStatuses []int `json:"invalid_status_code"`
|
||||
}
|
||||
|
||||
func StatusContains(status int, vals []int) bool {
|
||||
for _, v := range vals {
|
||||
if status == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *HttpStatusTest) RunTest(cfg *config.Config, headers map[string]string, args ...any) (bool, error) {
|
||||
// If body data, marshal to JSON
|
||||
var data io.Reader
|
||||
if h.Payload != nil {
|
||||
jsonData, err := json.Marshal(h.Payload)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
data = bytes.NewBuffer(jsonData)
|
||||
}
|
||||
|
||||
// Create new HTTP request
|
||||
client := analyzers.NewAnalyzeClient(cfg)
|
||||
req, err := http.NewRequest(h.Method, fmt.Sprintf(h.Endpoint, args...), data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Add custom headers if provided
|
||||
for key, value := range headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
// Execute HTTP Request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check response status code
|
||||
switch {
|
||||
case StatusContains(resp.StatusCode, h.ValidStatuses):
|
||||
return true, nil
|
||||
case StatusContains(resp.StatusCode, h.InvalidStatuses):
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.New("error checking response status code")
|
||||
}
|
||||
}
|
||||
|
||||
type Scopes struct {
|
||||
OrganizationScopes []Scope `json:"organization_scopes"`
|
||||
OAuthApplicationScopes []Scope `json:"oauth_application_scopes"`
|
||||
DatabaseScopes []Scope `json:"database_scopes"`
|
||||
DeployRequestScopes []Scope `json:"deploy_request_scopes"`
|
||||
BranchScopes []BranchScope `json:"branch_scopes"`
|
||||
BackupScopes []BranchScope `json:"backup_scopes"`
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
Name string `json:"name"`
|
||||
HttpTest HttpStatusTest `json:"test"`
|
||||
}
|
||||
|
||||
type BranchScope struct {
|
||||
Scope
|
||||
Production bool `json:"production"`
|
||||
}
|
||||
|
||||
func readInScopes() (*Scopes, error) {
|
||||
var scopes Scopes
|
||||
if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &scopes, nil
|
||||
}
|
||||
|
||||
func checkPermissions(cfg *config.Config, scopes []Scope, id, key string, args ...any) ([]string, error) {
|
||||
|
||||
permissions := make([]string, 0)
|
||||
for _, scope := range scopes {
|
||||
status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": fmt.Sprintf("%s:%s", id, key)}, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("running test: %w", err)
|
||||
}
|
||||
if status {
|
||||
permissions = append(permissions, scope.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func checkBranchPermissions(cfg *config.Config, scopes []BranchScope, id, key, organization, db, branch string, production bool) ([]string, error) {
|
||||
permissions := make([]string, 0)
|
||||
for _, scope := range scopes {
|
||||
// check if scope is for production or non production branch
|
||||
if production != scope.Production {
|
||||
continue
|
||||
}
|
||||
status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": fmt.Sprintf("%s:%s", id, key)}, organization, db, branch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("running test: %w", err)
|
||||
}
|
||||
if status {
|
||||
permissions = append(permissions, scope.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func checkBackupPermissions(cfg *config.Config, scopes []BranchScope, id, key, organization, db, backupId string, production bool) ([]string, error) {
|
||||
permissions := make([]string, 0)
|
||||
for _, scope := range scopes {
|
||||
// check if scope is for production or non production branch
|
||||
if production != scope.Production {
|
||||
continue
|
||||
}
|
||||
scope.HttpTest.Payload = map[string]string{"backup_id": backupId}
|
||||
status, err := scope.HttpTest.RunTest(cfg, map[string]string{"Authorization": fmt.Sprintf("%s:%s", id, key)}, organization, db)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("running test: %w", err)
|
||||
}
|
||||
if status {
|
||||
permissions = append(permissions, scope.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
type SecretInfo struct {
|
||||
Organization organization
|
||||
OrgPermissions []string
|
||||
DBPermissions map[Database][]string
|
||||
UnverifiedPermissions []string
|
||||
}
|
||||
|
||||
func AnalyzeAndPrintPermissions(cfg *config.Config, id, token string) {
|
||||
info, err := AnalyzePermissions(cfg, id, token)
|
||||
if err != nil {
|
||||
color.Red("[x] Error : %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
color.Green("[!] Valid PlanetScale credentials\n\n")
|
||||
color.Green("[i] Organization: %s\n\n", info.Organization.Name)
|
||||
printOrganizationPermissions(info.OrgPermissions)
|
||||
|
||||
if len(info.DBPermissions) > 0 {
|
||||
printDatabasePermissions(info.DBPermissions)
|
||||
}
|
||||
|
||||
printUnverifiedPermissions(info.UnverifiedPermissions)
|
||||
}
|
||||
|
||||
func AnalyzePermissions(cfg *config.Config, id, token string) (*SecretInfo, error) {
|
||||
var info = &SecretInfo{}
|
||||
|
||||
org, err := getOrganization(cfg, id, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.Organization = *org
|
||||
|
||||
scopes, err := readInScopes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading in scopes: %w", err)
|
||||
}
|
||||
|
||||
// organization permissions
|
||||
orgPermissions, err := getOrganizationPermissions(cfg, scopes, id, token, org.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.OrgPermissions = orgPermissions
|
||||
|
||||
// database permissions
|
||||
dbPermissions, err := getDatabasePermissions(cfg, scopes, id, token, org.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.DBPermissions = dbPermissions
|
||||
|
||||
// These are permissions that can not be verified,
|
||||
// either due to no endpoint available that specifically requires the permission
|
||||
// or there does not exist a way to verify these permissions without changing the state of the system (mostly DELETE permissions)
|
||||
info.UnverifiedPermissions = []string{
|
||||
PermissionStrings[ReadComment],
|
||||
PermissionStrings[CreateComment],
|
||||
PermissionStrings[ApproveDeployRequest],
|
||||
PermissionStrings[DeleteDatabases],
|
||||
PermissionStrings[DeleteDatabase],
|
||||
PermissionStrings[DeleteOauthTokens],
|
||||
PermissionStrings[DeleteBranch],
|
||||
PermissionStrings[DeleteBranchPassword],
|
||||
PermissionStrings[DeleteProductionBranch],
|
||||
PermissionStrings[DeleteProductionBranchPassword],
|
||||
PermissionStrings[DeleteBackups],
|
||||
PermissionStrings[DeleteProductionBranchBackups],
|
||||
PermissionStrings[WriteBackups],
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
type organization struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type organizationJSON struct {
|
||||
Data []organization `json:"data"`
|
||||
}
|
||||
|
||||
func getOrganization(cfg *config.Config, id, key string) (*organization, error) {
|
||||
url := "https://api.planetscale.com/v1/organizations"
|
||||
|
||||
var organizationJSON organizationJSON
|
||||
err := sendGetRequest(cfg, id, key, url, &organizationJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(organizationJSON.Data) == 0 {
|
||||
return nil, errors.New("invalid api credentials")
|
||||
}
|
||||
|
||||
return &organizationJSON.Data[0], nil
|
||||
}
|
||||
|
||||
func getOrganizationPermissions(cfg *config.Config, scopes *Scopes, id, token, orgName string) ([]string, error) {
|
||||
organizationPermissions, err := checkPermissions(cfg, scopes.OrganizationScopes, id, token, orgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oauthPermissions, err := getOAuthApplicationPermissions(cfg, scopes.OAuthApplicationScopes, id, token, orgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
organizationPermissions = append(organizationPermissions, oauthPermissions...)
|
||||
|
||||
return organizationPermissions, nil
|
||||
}
|
||||
|
||||
func getOAuthApplicationPermissions(cfg *config.Config, scopes []Scope, id, key, organization string) ([]string, error) {
|
||||
oauthApplicationId, err := getOAuthApplicationId(cfg, id, key, organization)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if oauthApplicationId != "" {
|
||||
oauthPermissions, err := checkPermissions(cfg, scopes, id, key, organization, oauthApplicationId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return oauthPermissions, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type oauthApplicationJSON struct {
|
||||
Data []struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
}
|
||||
|
||||
func getOAuthApplicationId(cfg *config.Config, id, key, organization string) (string, error) {
|
||||
url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/oauth-applications", organization)
|
||||
|
||||
var oauthApplicationJSON oauthApplicationJSON
|
||||
err := sendGetRequest(cfg, id, key, url, &oauthApplicationJSON)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(oauthApplicationJSON.Data) > 0 {
|
||||
return oauthApplicationJSON.Data[0].Id, nil
|
||||
}
|
||||
return "", nil // no oauth application found
|
||||
}
|
||||
|
||||
func getDatabasePermissions(cfg *config.Config, scopes *Scopes, id, token, orgName string) (map[Database][]string, error) {
|
||||
databases, err := getDatabases(cfg, id, token, orgName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dbPermissionsMap := make(map[Database][]string)
|
||||
for _, database := range databases {
|
||||
dbPermissions, err := checkPermissions(cfg, scopes.DatabaseScopes, id, token, orgName, database.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbPermissionsMap[database] = dbPermissions
|
||||
|
||||
branchPermissions, err := getBranchPermissions(cfg, scopes, id, token, orgName, database.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbPermissionsMap[database] = append(dbPermissionsMap[database], branchPermissions...)
|
||||
}
|
||||
|
||||
return dbPermissionsMap, nil
|
||||
}
|
||||
|
||||
func getBranchPermissions(cfg *config.Config, scopes *Scopes, id, token, orgName, dbName string) ([]string, error) {
|
||||
branches, err := getDbBranches(cfg, id, token, orgName, dbName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get permissions for prod and non prod branches
|
||||
prodDone, nonProdDone := false, false
|
||||
allBranchPermissions := make([]string, 0)
|
||||
for _, branch := range branches {
|
||||
// check if we have already checked permissions for prod or non prod branches
|
||||
if (prodDone && branch.Production) || (nonProdDone && !branch.Production) {
|
||||
continue
|
||||
}
|
||||
|
||||
if branch.Production {
|
||||
prodDone = true
|
||||
} else {
|
||||
nonProdDone = true
|
||||
}
|
||||
|
||||
branchPermissions, err := checkBranchPermissions(cfg, scopes.BranchScopes, id, token, orgName, dbName, branch.Name, branch.Production)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allBranchPermissions = append(allBranchPermissions, branchPermissions...)
|
||||
|
||||
backupId, err := getBackupId(cfg, id, token, orgName, dbName, branch.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if backupId != "" {
|
||||
backupPermissions, err := checkBackupPermissions(cfg, scopes.BackupScopes, id, token, orgName, dbName, backupId, branch.Production)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allBranchPermissions = append(allBranchPermissions, backupPermissions...)
|
||||
}
|
||||
|
||||
if prodDone && nonProdDone {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return allBranchPermissions, err
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
type databasesJSON struct {
|
||||
Data []Database `json:"data"`
|
||||
NextPageUrl string `json:"next_page_url"`
|
||||
}
|
||||
|
||||
func getDatabases(cfg *config.Config, id, key, organization string) ([]Database, error) {
|
||||
url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/databases", organization)
|
||||
databases := make([]Database, 0)
|
||||
|
||||
// loop for pagination
|
||||
for url != "" {
|
||||
var databasesResponse databasesJSON
|
||||
err := sendGetRequest(cfg, id, key, url, &databasesResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
databases = append(databases, databasesResponse.Data...)
|
||||
url = databasesResponse.NextPageUrl
|
||||
}
|
||||
|
||||
return databases, nil
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Production bool `json:"production"`
|
||||
}
|
||||
|
||||
type branchesJSON struct {
|
||||
Data []Branch `json:"data"`
|
||||
}
|
||||
|
||||
func getDbBranches(cfg *config.Config, id, key, organization, db string) ([]Branch, error) {
|
||||
url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/databases/%s/branches", organization, db)
|
||||
var branchesResponse branchesJSON
|
||||
err := sendGetRequest(cfg, id, key, url, &branchesResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return branchesResponse.Data, nil
|
||||
}
|
||||
|
||||
type backupsJson struct {
|
||||
Data []struct {
|
||||
Id string `json:"id"`
|
||||
}
|
||||
}
|
||||
|
||||
func getBackupId(cfg *config.Config, id, key, organization, db, branch string) (string, error) {
|
||||
url := fmt.Sprintf("https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/backups", organization, db, branch)
|
||||
var backupsResponse backupsJson
|
||||
err := sendGetRequest(cfg, id, key, url, &backupsResponse)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(backupsResponse.Data) > 0 {
|
||||
return backupsResponse.Data[0].Id, nil
|
||||
}
|
||||
return "", nil // no backups found
|
||||
}
|
||||
|
||||
func sendGetRequest(cfg *config.Config, id, key, url string, responseObj interface{}) error {
|
||||
client := analyzers.NewAnalyzeClient(cfg)
|
||||
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("%s:%s", id, key))
|
||||
|
||||
// Execute HTTP Request
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check response status code
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
// Decode response body
|
||||
err = json.NewDecoder(resp.Body).Decode(&responseObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil // response successfully decoded
|
||||
case http.StatusForbidden:
|
||||
return nil // no permission
|
||||
default:
|
||||
return fmt.Errorf("unexpected status code %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func printOrganizationPermissions(permissions []string) {
|
||||
color.Yellow("[i] Organization Permissions:")
|
||||
|
||||
if len(permissions) == 0 {
|
||||
color.Yellow("No permissions found")
|
||||
} else {
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"Permission"})
|
||||
for _, permission := range permissions {
|
||||
t.AppendRow(table.Row{color.GreenString(permission)})
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
}
|
||||
|
||||
func printDatabasePermissions(permissions map[Database][]string) {
|
||||
color.Yellow("[i] Database Permissions:")
|
||||
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"Database", "Permission"})
|
||||
for database, dbPermissions := range permissions {
|
||||
t.AppendRow(table.Row{database.Name, color.GreenString(strings.Join(dbPermissions, ", "))})
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
|
||||
func printUnverifiedPermissions(permissions []string) {
|
||||
color.Yellow("[i] Unverified Permissions:")
|
||||
|
||||
t := table.NewWriter()
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.AppendHeader(table.Row{"Permission"})
|
||||
for _, permission := range permissions {
|
||||
t.AppendRow(table.Row{color.YellowString(permission)})
|
||||
}
|
||||
t.Render()
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package planetscale
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
|
||||
)
|
||||
|
||||
//go:embed expected_output.json
|
||||
var expectedOutput []byte
|
||||
|
||||
func TestAnalyzer_Analyze(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*15)
|
||||
defer cancel()
|
||||
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5")
|
||||
if err != nil {
|
||||
t.Fatalf("could not get test secrets from GCP: %s", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
token string
|
||||
want string // JSON string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid planetscale id and key",
|
||||
id: testSecrets.MustGetField("PLANET_SCALE_ID"),
|
||||
token: testSecrets.MustGetField("PLANET_SCALE_TOKEN"),
|
||||
want: string(expectedOutput),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := Analyzer{Cfg: &config.Config{}}
|
||||
got, err := a.Analyze(ctx, map[string]string{"id": tt.id, "token": tt.token})
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(got.Bindings)
|
||||
|
||||
// Marshal the actual result to JSON
|
||||
gotJSON, err := json.Marshal(got)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Parse the expected JSON string
|
||||
var wantObj analyzers.AnalyzerResult
|
||||
if err := json.Unmarshal([]byte(tt.want), &wantObj); err != nil {
|
||||
t.Fatalf("could not unmarshal want JSON string: %s", err)
|
||||
}
|
||||
|
||||
// bindings need to be in the same order to be comparable
|
||||
sortBindings(wantObj.Bindings)
|
||||
|
||||
// Marshal the expected result to JSON (to normalize)
|
||||
wantJSON, err := json.Marshal(wantObj)
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to JSON: %s", err)
|
||||
}
|
||||
|
||||
// Compare the JSON strings
|
||||
if string(gotJSON) != string(wantJSON) {
|
||||
// Pretty-print both JSON strings for easier comparison
|
||||
var gotIndented, wantIndented []byte
|
||||
gotIndented, err = json.MarshalIndent(got, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal got to indented JSON: %s", err)
|
||||
}
|
||||
wantIndented, err = json.MarshalIndent(wantObj, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("could not marshal want to indented JSON: %s", err)
|
||||
}
|
||||
t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to sort bindings
|
||||
func sortBindings(bindings []analyzers.Binding) {
|
||||
sort.SliceStable(bindings, func(i, j int) bool {
|
||||
if bindings[i].Resource.Name == bindings[j].Resource.Name {
|
||||
return bindings[i].Permission.Value < bindings[j].Permission.Value
|
||||
}
|
||||
return bindings[i].Resource.Name < bindings[j].Resource.Name
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
{
|
||||
"organization_scopes": [
|
||||
{
|
||||
"name": "read_organization",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_invoices",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/invoices",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_databases",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_audit_logs",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/audit-log",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_databases",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_oauth_applications",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/oauth-applications",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
}
|
||||
],
|
||||
"oauth_application_scopes": [
|
||||
{
|
||||
"name": "write_oauth_tokens",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/oauth-applications/%s/token",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_oauth_tokens",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/oauth-applications/%s/tokens",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
}
|
||||
],
|
||||
"database_scopes": [
|
||||
{
|
||||
"name": "read_database",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "write_database",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s",
|
||||
"method": "PATCH",
|
||||
"valid_status_code": [400],
|
||||
"invalid_status_code": [403],
|
||||
"payload": {
|
||||
"default_branch": "`nowaythisbranchcanexist"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_branch",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_branch",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_deploy_request",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/deploy-requests",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "create_deploy_request",
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/deploy-requests",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
}
|
||||
],
|
||||
"branch_scopes": [
|
||||
{
|
||||
"name": "connect_branch",
|
||||
"production": false,
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/passwords",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403],
|
||||
"payload": {
|
||||
"role": "`nowaythisrolecanexist"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "connect_production_branch",
|
||||
"production": true,
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/passwords",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403],
|
||||
"payload": {
|
||||
"role": "`nowaythisrolecanexist"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "read_backups",
|
||||
"production": true,
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches/%s/backups",
|
||||
"method": "GET",
|
||||
"valid_status_code": [200],
|
||||
"invalid_status_code": [403]
|
||||
}
|
||||
}
|
||||
],
|
||||
"backup_scopes": [
|
||||
{
|
||||
"name": "restore_backup",
|
||||
"production": false,
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403],
|
||||
"payload": {
|
||||
"backup_id": "%s"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "restore_production_branch_backup",
|
||||
"production": true,
|
||||
"test": {
|
||||
"endpoint": "https://api.planetscale.com/v1/organizations/%s/databases/%s/branches",
|
||||
"method": "POST",
|
||||
"valid_status_code": [422],
|
||||
"invalid_status_code": [403],
|
||||
"payload": {
|
||||
"backup_id": "%s"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/notion"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/openai"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/opsgenie"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/planetscale"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/postgres"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/postman"
|
||||
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/privatekey"
|
||||
@@ -103,5 +104,7 @@ func Run(keyType string, secretInfo SecretInfo) {
|
||||
digitalocean.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "elevenlabs":
|
||||
elevenlabs.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
|
||||
case "planetscale":
|
||||
planetscale.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["id"], secretInfo.Parts["token"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,10 @@ package planetscale
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
regexp "github.com/wasilibs/go-re2"
|
||||
"net/http"
|
||||
|
||||
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"
|
||||
@@ -66,6 +67,10 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode >= 200 && res.StatusCode < 300 {
|
||||
s1.Verified = true
|
||||
s1.AnalysisInfo = map[string]string{
|
||||
"id": username,
|
||||
"token": password,
|
||||
}
|
||||
} else if res.StatusCode == 401 {
|
||||
// The secret is determinately not verified
|
||||
s1.Verified = false
|
||||
|
||||
@@ -139,7 +139,7 @@ func TestPlanetscale_FromChunk(t *testing.T) {
|
||||
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
|
||||
}
|
||||
}
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "verificationError")
|
||||
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError", "AnalysisInfo", "ExtraData")
|
||||
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
|
||||
t.Errorf("Planetscale.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
|
||||
}
|
||||
|
||||
@@ -68,6 +68,17 @@ func New(c common.Common, keyType string) *AnalyzeForm {
|
||||
Required: true,
|
||||
RedactInput: true,
|
||||
}}
|
||||
case "planetscale":
|
||||
inputs = []textinputs.InputConfig{{
|
||||
Label: "Service Id",
|
||||
Key: "id",
|
||||
Required: true,
|
||||
}, {
|
||||
Label: "Service Token",
|
||||
Key: "token",
|
||||
Required: true,
|
||||
RedactInput: true,
|
||||
}}
|
||||
default:
|
||||
inputs = []textinputs.InputConfig{{
|
||||
Label: "Secret",
|
||||
|
||||
Reference in New Issue
Block a user