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.
This commit is contained in:
Cody Rose 2023-08-03 14:02:31 -04:00 committed by GitHub
parent e322c4b29d
commit d763097fdf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 37 deletions

View file

@ -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 {
defer l.Close()
// Non-TLS verify
err = l.Bind(username, password)
if err == nil {
return true
}
// STARTTLS
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err == nil {
// STARTTLS verify
err = l.Bind(username, password)
if err == nil {
return true
}
}
if err != nil {
return err
}
defer l.Close()
// Non-TLS verify
err = l.Bind(username, password)
if err == nil {
return nil
}
// 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 {

View file

@ -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")
}
}