Skip to content

Commit

Permalink
implement indeterminate LDAP verification (#1574)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rosecodym authored Aug 3, 2023
1 parent e322c4b commit d763097
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 35 deletions.
72 changes: 46 additions & 26 deletions pkg/detectors/ldap/ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package ldap
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/url"
"regexp"
"strings"
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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 {
Expand Down
43 changes: 34 additions & 9 deletions pkg/detectors/ldap/ldap_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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{},
Expand All @@ -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)
}
})
Expand All @@ -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")
}
}

Expand Down

0 comments on commit d763097

Please sign in to comment.