From d763097fdf11e392f4df5f9ab50f3ace859a1a30 Mon Sep 17 00:00:00 2001 From: Cody Rose Date: Thu, 3 Aug 2023 14:02:31 -0400 Subject: [PATCH] implement indeterminate LDAP verification (#1574) This PR implements tri-state verification for the LDAP detector. This implementation looks for network errors to explicitly flag as indeterminate, rather than authentication errors to explicitly flag as determinate; this is because the error that occurs from authentication failures doesn't appear to have its own type and I didn't want to have to match on the error message text. --- pkg/detectors/ldap/ldap.go | 72 +++++++++++++-------- pkg/detectors/ldap/ldap_integration_test.go | 43 +++++++++--- 2 files changed, 80 insertions(+), 35 deletions(-) diff --git a/pkg/detectors/ldap/ldap.go b/pkg/detectors/ldap/ldap.go index 0fbeb496434f..fb5fd15df687 100644 --- a/pkg/detectors/ldap/ldap.go +++ b/pkg/detectors/ldap/ldap.go @@ -3,6 +3,8 @@ package ldap import ( "context" "crypto/tls" + "fmt" + "net" "net/url" "regexp" "strings" @@ -63,7 +65,11 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - s1.Verified = verifyLDAP(ctx, username[1], password[1], ldapURL) + verificationErr := verifyLDAP(username[1], password[1], ldapURL) + s1.Verified = verificationErr == nil + if !isErrDeterminate(verificationErr) { + s1.VerificationError = verificationErr + } } results = append(results, s1) @@ -89,7 +95,12 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result } if verify { - s1.Verified = verifyLDAP(ctx, username, password, ldapURL) + verificationError := verifyLDAP(username, password, ldapURL) + + s1.Verified = verificationError == nil + if !isErrDeterminate(verificationError) { + s1.VerificationError = verificationError + } } results = append(results, s1) @@ -98,7 +109,19 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result return results, nil } -func verifyLDAP(ctx context.Context, username, password string, ldapURL *url.URL) bool { +func isErrDeterminate(err error) bool { + switch e := err.(type) { + case *ldap.Error: + switch e.Err.(type) { + case *net.OpError: + return false + } + } + + return true +} + +func verifyLDAP(username, password string, ldapURL *url.URL) error { // Tests with non-TLS, TLS, and STARTTLS ldap.DefaultTimeout = 5 * time.Second @@ -109,38 +132,35 @@ func verifyLDAP(ctx context.Context, username, password string, ldapURL *url.URL case "ldap": // Non-TLS dial l, err := ldap.DialURL(uri) + if err != nil { + return err + } + defer l.Close() + // Non-TLS verify + err = l.Bind(username, password) if err == nil { - defer l.Close() - // Non-TLS verify - err = l.Bind(username, password) - if err == nil { - return true - } + return nil + } - // STARTTLS - err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err == nil { - // STARTTLS verify - err = l.Bind(username, password) - if err == nil { - return true - } - } + // STARTTLS + err = l.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err } + // STARTTLS verify + return l.Bind(username, password) case "ldaps": // TLS dial l, err := ldap.DialTLS("tcp", uri, &tls.Config{InsecureSkipVerify: true}) - if err == nil { - defer l.Close() - // TLS verify - err = l.Bind(username, password) - if err == nil { - return true - } + if err != nil { + return err } + defer l.Close() + // TLS verify + return l.Bind(username, password) } - return false + return fmt.Errorf("unknown ldap scheme %q", ldapURL.Scheme) } func (s Scanner) Type() detectorspb.DetectorType { diff --git a/pkg/detectors/ldap/ldap_integration_test.go b/pkg/detectors/ldap/ldap_integration_test.go index 3c7a2779ef6f..8b85d382bcc2 100644 --- a/pkg/detectors/ldap/ldap_integration_test.go +++ b/pkg/detectors/ldap/ldap_integration_test.go @@ -7,13 +7,14 @@ import ( "bytes" "context" "errors" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "os" "os/exec" "strings" "testing" "time" - "github.com/kylelemons/godebug/pretty" "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" @@ -60,11 +61,12 @@ func TestLdap_Integration_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 with URI and separate user+password usage, verified", @@ -152,6 +154,26 @@ func TestLdap_Integration_FromChunk(t *testing.T) { }, wantErr: false, }, + { + name: "inaccessible host", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(` + ldap://badhost:1389 + binddn="cn=admin,dc=example,dc=org" + pass="P@55w0rd"`), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_LDAP, + Verified: false, + }, + }, + wantErr: false, + wantVerificationErr: true, + }, { name: "not found", s: Scanner{}, @@ -176,9 +198,12 @@ func TestLdap_Integration_FromChunk(t *testing.T) { if len(got[i].Raw) == 0 { t.Fatalf("no raw secret present: \n %+v", got[i]) } - got[i].Raw = nil + if (got[i].VerificationError != nil) != tt.wantVerificationErr { + t.Fatalf("wantVerificationError = %v, verification error = %v", tt.wantVerificationErr, got[i].VerificationError) + } } - if diff := pretty.Compare(got, tt.want); diff != "" { + ignoreOpts := cmpopts.IgnoreFields(detectors.Result{}, "Raw", "VerificationError") + if diff := cmp.Diff(got, tt.want, ignoreOpts); diff != "" { t.Errorf("Ldap.FromData() %s diff: (-got +want)\n%s", tt.name, diff) } }) @@ -205,7 +230,7 @@ func startOpenLDAP() error { return nil case <-time.After(30 * time.Second): stopOpenLDAP() - return errors.New("timeout waiting for postgres database to be ready") + return errors.New("timeout waiting for ldap service to be ready") } }