-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feat/oss-101-anthropic-analyzer
- Loading branch information
Showing
24 changed files
with
1,458 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
//go:generate generate_permissions permissions.yaml permissions.go elevenlabs | ||
package dockerhub | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
|
||
"github.com/fatih/color" | ||
"github.com/jedib0t/go-pretty/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 | ||
} | ||
|
||
// SecretInfo hold the information about the token generated from username and pat | ||
type SecretInfo struct { | ||
User User | ||
Valid bool | ||
Reference string | ||
Permissions []string | ||
Repositories []Repository | ||
ExpiresIn string | ||
Misc map[string]string | ||
} | ||
|
||
// User hold the information about user to whom the personal access token belongs | ||
type User struct { | ||
ID string | ||
Username string | ||
Email string | ||
} | ||
|
||
// Repository hold information about each repository the user can access | ||
type Repository struct { | ||
ID string | ||
Name string | ||
Type string | ||
IsPrivate bool | ||
StarCount int | ||
PullCount int | ||
} | ||
|
||
func (a Analyzer) Type() analyzers.AnalyzerType { | ||
return analyzers.AnalyzerTypeDockerHub | ||
} | ||
|
||
func (a Analyzer) Analyze(_ context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) { | ||
username, exist := credInfo["username"] | ||
if !exist { | ||
return nil, errors.New("username not found in the credentials info") | ||
} | ||
|
||
pat, exist := credInfo["pat"] | ||
if !exist { | ||
return nil, errors.New("personal access token(PAT) not found in the credentials info") | ||
} | ||
|
||
info, err := AnalyzePermissions(a.Cfg, username, pat) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return secretInfoToAnalyzerResult(info), nil | ||
} | ||
|
||
// AnalyzePermissions will collect all the scopes assigned to token along with resource it can access | ||
func AnalyzePermissions(cfg *config.Config, username, pat string) (*SecretInfo, error) { | ||
// create the http client | ||
client := analyzers.NewAnalyzeClientUnrestricted(cfg) // `/user/login` is a non-safe request | ||
|
||
var secretInfo = &SecretInfo{} | ||
|
||
// try to login and get jwt token | ||
token, err := login(client, username, pat) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if err := decodeTokenToSecretInfo(token, secretInfo); err != nil { | ||
return nil, err | ||
} | ||
|
||
// fetch repositories using the jwt token and translate them to secret info | ||
if err := fetchRepositories(client, username, token, secretInfo); err != nil { | ||
return nil, err | ||
} | ||
|
||
// return secret info | ||
return secretInfo, nil | ||
} | ||
|
||
func AnalyzeAndPrintPermissions(cfg *config.Config, username, pat string) { | ||
info, err := AnalyzePermissions(cfg, username, pat) | ||
if err != nil { | ||
// just print the error in cli and continue as a partial success | ||
color.Red("[x] Error : %s", err.Error()) | ||
} | ||
|
||
if info == nil { | ||
color.Red("[x] Error : %s", "No information found") | ||
return | ||
} | ||
|
||
if info.Valid { | ||
color.Green("[!] Valid DockerHub Credentials\n\n") | ||
// print user information | ||
printUser(info.User) | ||
// print permissions | ||
printPermissions(info.Permissions) | ||
// print repositories | ||
printRepositories(info.Repositories) | ||
|
||
color.Yellow("\n[i] Expires: %s", info.ExpiresIn) | ||
} | ||
} | ||
|
||
// secretInfoToAnalyzerResult translate secret info to Analyzer Result | ||
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { | ||
if info == nil { | ||
return nil | ||
} | ||
|
||
result := analyzers.AnalyzerResult{ | ||
AnalyzerType: analyzers.AnalyzerTypeDockerHub, | ||
Metadata: map[string]any{"Valid_Key": info.Valid}, | ||
Bindings: make([]analyzers.Binding, len(info.Repositories)), | ||
} | ||
|
||
// extract information to create bindings and append to result bindings | ||
for _, repo := range info.Repositories { | ||
binding := analyzers.Binding{ | ||
Resource: analyzers.Resource{ | ||
Name: repo.Name, | ||
FullyQualifiedName: repo.ID, | ||
Type: repo.Type, | ||
Metadata: map[string]any{ | ||
"is_private": repo.IsPrivate, | ||
"pull_count": repo.PullCount, | ||
"star_count": repo.StarCount, | ||
}, | ||
}, | ||
Permission: analyzers.Permission{ | ||
// as all permissions are against repo, we assign the highest available permission | ||
Value: assignHighestPermission(info.Permissions), | ||
}, | ||
} | ||
|
||
result.Bindings = append(result.Bindings, binding) | ||
} | ||
|
||
return &result | ||
} | ||
|
||
// cli print functions | ||
func printUser(user User) { | ||
color.Green("\n[i] User:") | ||
t := table.NewWriter() | ||
t.SetOutputMirror(os.Stdout) | ||
t.AppendHeader(table.Row{"ID", "Username", "Email"}) | ||
t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.Username), color.GreenString(user.Email)}) | ||
t.Render() | ||
} | ||
|
||
func printPermissions(permissions []string) { | ||
color.Yellow("[i] Permissions:") | ||
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 printRepositories(repos []Repository) { | ||
color.Green("\n[i] Repositories:") | ||
t := table.NewWriter() | ||
t.SetOutputMirror(os.Stdout) | ||
t.AppendHeader(table.Row{"Type", "ID(username/repo/repo_type/repo_name)", "Name", "Is Private", "Pull Count", "Star Count"}) | ||
for _, repo := range repos { | ||
t.AppendRow(table.Row{color.GreenString(repo.Type), color.GreenString(repo.ID), color.GreenString(repo.Name), | ||
color.GreenString("%t", repo.IsPrivate), color.GreenString("%d", repo.PullCount), color.GreenString("%d", repo.StarCount)}) | ||
} | ||
t.Render() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package dockerhub | ||
|
||
import ( | ||
_ "embed" | ||
"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" | ||
) | ||
|
||
//go:embed result_output.json | ||
var expectedOutput []byte | ||
|
||
func TestAnalyzer_Analyze(t *testing.T) { | ||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*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) | ||
} | ||
|
||
username := testSecrets.MustGetField("DOCKERHUB_USERNAME") | ||
pat := testSecrets.MustGetField("DOCKERHUB_PAT") | ||
|
||
tests := []struct { | ||
name string | ||
username string | ||
pat string | ||
want []byte // JSON string | ||
wantErr bool | ||
}{ | ||
{ | ||
name: "valid dockerhub credentials", | ||
username: username, | ||
pat: pat, | ||
want: 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{"username": tt.username, "pat": tt.pat}) | ||
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) | ||
} | ||
}) | ||
} | ||
} |
Oops, something went wrong.