[analyze] Add Analyzer for SourceGraph (#3173)

* implement analyzer interface for sourcegraph

* created permission for sourcegraph
test for sourcegraph.
added email in resource metadata.

* handling of missing keys in map

* linked sourcegraph detector to analyzer

* update the fullyqualidied name of resource to make it unique.
updated the test.

* add current user email in metadata

---------

Co-authored-by: Abdul Basit <abasit@folio3.com>
This commit is contained in:
Abdul Basit 2024-09-07 00:40:47 +05:00 committed by GitHub
parent 4cab071032
commit a43d451c4d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 233 additions and 1 deletions

View file

@ -0,0 +1,66 @@
// Code generated by go generate; DO NOT EDIT.
package sourcegraph
import "errors"
type Permission int
const (
NoAccess Permission = iota
UserRead Permission = iota
SiteAdminFull Permission = iota
)
var (
PermissionStrings = map[Permission]string{
UserRead: "user:read",
SiteAdminFull: "site_admin:full",
}
StringToPermission = map[string]Permission{
"user:read": UserRead,
"site_admin:full": SiteAdminFull,
}
PermissionIDs = map[Permission]int{
UserRead: 0,
SiteAdminFull: 1,
}
IdToPermission = map[int]Permission{
0: UserRead,
1: SiteAdminFull,
}
)
// 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")
}

View file

@ -0,0 +1,3 @@
permissions:
- user:read
- site_admin:full

View file

@ -1,3 +1,4 @@
//go:generate generate_permissions permissions.yaml permissions.go sourcegraph
package sourcegraph
// ToDo: Add suport for custom domain
@ -11,8 +12,64 @@ import (
"github.com/fatih/color"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/pb/analyzerpb"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
var _ analyzers.Analyzer = (*Analyzer)(nil)
type Analyzer struct {
Cfg *config.Config
}
func (Analyzer) Type() analyzerpb.AnalyzerType { return analyzerpb.AnalyzerType_Sourcegraph }
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
key, ok := credInfo["key"]
if !ok {
return nil, fmt.Errorf("missing key in credInfo")
}
info, err := AnalyzePermissions(a.Cfg, key)
if err != nil {
return nil, err
}
return secretInfoToAnalyzerResult(info), nil
}
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
permission := PermissionStrings[UserRead]
if info.IsSiteAdmin {
permission = PermissionStrings[SiteAdminFull]
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzerpb.AnalyzerType_Sourcegraph,
Metadata: nil,
Bindings: []analyzers.Binding{
{
Resource: analyzers.Resource{
Name: info.User.Data.CurrentUser.Username,
FullyQualifiedName: "sourcegraph/" + info.User.Data.CurrentUser.Email,
Type: "user",
Metadata: map[string]any{
"created_at": info.User.Data.CurrentUser.CreatedAt,
"email": info.User.Data.CurrentUser.Email,
},
Parent: nil,
},
Permission: analyzers.Permission{
Value: permission,
},
},
},
}
return &result
}
type GraphQLError struct {
Message string `json:"message"`
Path []string `json:"path"`

View file

@ -0,0 +1,103 @@
package sourcegraph
import (
"encoding/json"
"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"
)
func TestAnalyzer_Analyze(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors4")
if err != nil {
t.Fatalf("could not get test secrets from GCP: %s", err)
}
secret := testSecrets.MustGetField("SOURCEGRAPH")
tests := []struct {
name string
key string
want string // JSON string
wantErr bool
}{
{
name: "valid SourceGraph key",
key: secret,
want: `{
"AnalyzerType": 17,
"Bindings": [
{
"Resource": {
"Name": "ahrav",
"FullyQualifiedName": "sourcegraph/ahravdutta02@gmail.com",
"Type": "user",
"Metadata": {
"created_at": "2023-07-23T04:16:31Z",
"email": "ahravdutta02@gmail.com"
},
"Parent": null
},
"Permission": {
"Value": "user:read",
"Parent": null
}
}
],
"UnboundedResources": null,
"Metadata": null
}`,
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{"key": tt.key})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// 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)
}
// 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)
}
})
}
}

View file

@ -3,10 +3,11 @@ package sourcegraph
import (
"context"
"fmt"
regexp "github.com/wasilibs/go-re2"
"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"
@ -80,6 +81,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
} else {
s1.SetVerificationError(err, resMatch)
}
s1.AnalysisInfo = map[string]string{"key": resMatch}
}
results = append(results, s1)

View file

@ -227,6 +227,7 @@ func TestSourcegraph_FromChunk(t *testing.T) {
if (got[i].VerificationError() != nil) != tt.wantVerificationErr {
t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError())
}
got[i].AnalysisInfo = nil
}
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError", "ExtraData")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {