Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 57 additions & 36 deletions pkg/detectors/okta/okta.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ package okta
import (
"context"
"fmt"
"io"
"net/http"
"strings"

regexp "github.com/wasilibs/go-re2"

"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)

type Scanner struct{
type Scanner struct {
client *http.Client
detectors.DefaultMultiPartCredentialProvider
}

// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)

var (
domainPat = regexp.MustCompile(`\b[a-z0-9-]{1,40}\.okta(?:preview|-emea){0,1}\.com\b`)
tokenPat = regexp.MustCompile(`\b00[a-zA-Z0-9_-]{40}\b`)
defaultClient = detectors.DetectorHttpClientWithNoLocalAddresses
domainPat = regexp.MustCompile(`\b[a-z0-9-]{1,40}\.okta(?:preview|-emea){0,1}\.com\b`)
tokenPat = regexp.MustCompile(`\b00[a-zA-Z0-9_-]{40}\b`)
// TODO: Oauth client secrets
)

Expand All @@ -34,55 +34,76 @@ func (s Scanner) Keywords() []string {

// FromData will find and optionally verify Okta secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
for _, tokenMatch := range tokenPat.FindAll(data, -1) {
token := string(tokenMatch)
dataStr := string(data)

for _, domainMatch := range domainPat.FindAll(data, -1) {
domain := string(domainMatch)
var uniqueTokens, uniqueDomains = make(map[string]struct{}), make(map[string]struct{})

result := detectors.Result{
for _, matches := range tokenPat.FindAllStringSubmatch(dataStr, -1) {
uniqueTokens[matches[0]] = struct{}{}
}

for _, matches := range domainPat.FindAllStringSubmatch(dataStr, -1) {
uniqueDomains[matches[0]] = struct{}{}
}

for token := range uniqueTokens {
for domain := range uniqueDomains {
s1 := detectors.Result{
DetectorType: detectorspb.DetectorType_Okta,
Raw: []byte(token),
RawV2: []byte(fmt.Sprintf("%s:%s", domain, token)),
}

if verify {
// curl -v -X GET \
// -H "Accept: application/json" \
// -H "Content-Type: application/json" \
// -H "Authorization: Bearer token" \
// "https://subdomain.okta.com/api/v1/users/me"
//

url := fmt.Sprintf("https://%s/api/v1/users/me", domain)
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return results, err
client := s.client
if client == nil {
client = defaultClient
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("SSWS %s", token))

resp, err := detectors.DetectorHttpClientWithNoLocalAddresses.Do(req)
if err != nil {
continue
}
defer resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
body, _ := io.ReadAll(resp.Body)
if strings.Contains(string(body), "activated") {
result.Verified = true
}
}
isVerified, verificationErr := verifyOktaToken(ctx, client, domain, token)
s1.Verified = isVerified
s1.SetVerificationError(verificationErr)
}

results = append(results, result)
results = append(results, s1)
}
}

return
}

func verifyOktaToken(ctx context.Context, client *http.Client, domain string, token string) (bool, error) {
// curl -v -X GET \
// -H "Accept: application/json" \
// -H "Content-Type: application/json" \
// -H "Authorization: SSWS token" \
// "https://subdomain.okta.com/api/v1/users/me"

url := fmt.Sprintf("https://%s/api/v1/users/me", domain)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return false, err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("SSWS %s", token))

resp, err := client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()

switch resp.StatusCode {
case http.StatusOK:
return true, nil
case http.StatusUnauthorized, http.StatusForbidden:
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
}

func (s Scanner) Type() detectorspb.DetectorType {
return detectorspb.DetectorType_Okta
}
Expand Down
57 changes: 41 additions & 16 deletions pkg/detectors/okta/okta_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"testing"
"time"

"github.com/kylelemons/godebug/pretty"
"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"
Expand All @@ -32,11 +34,12 @@ func TestOkta_FromChunk(t *testing.T) {
verify bool
}
tests := []struct {
name string
s Scanner
args args
want []detectors.Result
wantErr bool
name string
s Scanner
args args
want []detectors.Result
wantErr bool
wantVerificationErr bool
}{
{
name: "found token, verified",
Expand All @@ -52,7 +55,8 @@ func TestOkta_FromChunk(t *testing.T) {
Verified: true,
},
},
wantErr: false,
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, unverified",
Expand All @@ -68,7 +72,8 @@ func TestOkta_FromChunk(t *testing.T) {
Verified: false,
},
},
wantErr: false,
wantErr: false,
wantVerificationErr: false,
},
{
name: "not found",
Expand All @@ -78,26 +83,46 @@ func TestOkta_FromChunk(t *testing.T) {
data: []byte("You cannot find the secret within"),
verify: true,
},
want: nil,
wantErr: false,
want: nil,
wantErr: false,
wantVerificationErr: false,
},
{
name: "found, verified but unexpected api surface",
s: Scanner{client: common.ConstantResponseHttpClient(404, "")},
args: args{
ctx: context.Background(),
data: []byte(fmt.Sprintf("You can find a okta secret %s within oktaDomain %s", secretInactive, domain)),
verify: true,
},
want: []detectors.Result{
{
DetectorType: detectorspb.DetectorType_Okta,
Verified: false,
},
},
wantErr: false,
wantVerificationErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := Scanner{}
got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
got, err := tt.s.FromData(tt.args.ctx, tt.args.verify, tt.args.data)
if (err != nil) != tt.wantErr {
t.Errorf("Okta.FromData) error = %v, wantErr %v", err, tt.wantErr)
return
}
for i := range got {
if len(got[i].Raw) == 0 {
t.Fatal("no raw secret present")
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())
}
got[i].Raw = nil
}
if diff := pretty.Compare(got, tt.want); diff != "" {
t.Errorf("Okta.FromData) %s diff: (-got +want)\n%s", tt.name, diff)
ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "RawV2", "verificationError")
if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" {
t.Errorf("Okta.FromData() %s diff: (-got +want)\n%s", tt.name, diff)
}
})
}
Expand Down