diff options
| author | Marin Ivanov <[email protected]> | 2019-02-13 04:44:35 +0200 |
|---|---|---|
| committer | Marin Ivanov <[email protected]> | 2019-02-13 05:39:06 +0200 |
| commit | 0409e4c6689a5db50a618429e9eb40cdf704df4e (patch) | |
| tree | 800a666a6581906a65290ca03f5c2cb1a3779e80 | |
| parent | 518f72942c1cf751010a532c6189cd0eb0a5323b (diff) | |
Add option to require TLS upgrade, before serving any requests
| -rw-r--r-- | messages.go | 3 | ||||
| -rw-r--r-- | server.go | 31 | ||||
| -rw-r--r-- | server_test.go | 135 |
3 files changed, 166 insertions, 3 deletions
diff --git a/messages.go b/messages.go new file mode 100644 index 0000000..a0d1450 --- /dev/null +++ b/messages.go @@ -0,0 +1,3 @@ +package ldap + +const errorEnforceTLSRequiresTLSConfig = "EnforceTLS requires TLSConfig set" @@ -2,6 +2,7 @@ package ldap import ( "crypto/tls" + "errors" "io" "log" "net" @@ -60,7 +61,8 @@ type Server struct { Stats *Stats // If set, server will accept StartTLS. - TLSConfig *tls.Config + TLSConfig *tls.Config + EnforceTLS bool closing chan struct{} } @@ -187,6 +189,10 @@ func (server *Server) ListenAndServe(listenString string) error { } func (server *Server) Serve(ln net.Listener) error { + if server.TLSConfig == nil && server.EnforceTLS { + return errors.New(errorEnforceTLSRequiresTLSConfig) + } + newConn := make(chan net.Conn) go func() { for { @@ -262,6 +268,19 @@ handler: } } + // Enforce TLS + switch conn.(type) { + case *tls.Conn: + default: + if server.EnforceTLS && req.Tag != ApplicationExtendedRequest { + responsePacket := encodeLDAPResponse(messageID, ApplicationExtendedResponse, LDAPResultProtocolError, "Upgrade to TLS is required") + if err = sendPacket(conn, responsePacket); err != nil { + log.Printf("sendPacket error %s", err.Error()) + } + break handler + } + } + //log.Printf("DEBUG: handling operation: %s [%d]", ApplicationMap[req.Tag], req.Tag) //ber.PrintPacket(packet) // DEBUG @@ -318,8 +337,12 @@ handler: } var ldapResultCode LDAPResultCode if tlsConn == nil { - // Wasn't an upgrade. Pass through. - ldapResultCode = HandleExtendedRequest(req, boundDN, server.ExtendedFns, conn) + // Wasn't an upgrade. + if server.EnforceTLS { + ldapResultCode = LDAPResultProtocolError + } else { + ldapResultCode = HandleExtendedRequest(req, boundDN, server.ExtendedFns, conn) + } } else { ldapResultCode = LDAPResultSuccess } @@ -330,6 +353,8 @@ handler: } if tlsConn != nil { conn = tlsConn + } else if server.EnforceTLS { + break handler } case ApplicationAbandonRequest: HandleAbandonRequest(req, boundDN, server.AbandonFns, conn) diff --git a/server_test.go b/server_test.go index 9b9d579..9cde9d7 100644 --- a/server_test.go +++ b/server_test.go @@ -230,6 +230,141 @@ which is very heavy-handed for a test like this. } } +func TestEnforcedTLSWithoutTLSConfig(t *testing.T) { + s := NewServer() + defer s.Close() + s.EnforceTLS = true + s.Bind = BindAnonOK + s.Search = SearchSimple + + ln, _ := mustListen() + done := make(chan error) + go func() { + if err := s.Serve(ln); err != nil { + done <- err + } + }() + + select { + case err := <-done: + msg := err.Error() + if msg != errorEnforceTLSRequiresTLSConfig { + t.Errorf("Unexpected server error: %s", msg) + } + case <-time.After(timeout): + t.Error("server did not return an error") + } +} +func TestEnforcedTLS(t *testing.T) { + if runtime.GOOS == "darwin" { + defer func() { + if t.Failed() { + t.Logf(`NOTE: this test won't pass with the built-in Mac ldap utilities. +Work around this by using brew install openldap, and running the test as PATH=/usr/local/opt/openldap/bin:$PATH go test. + +This test uses environment variables that are respected by OpenLDAP, but the Mac utilities don't let you override +security settings through environment variables; they expect certificates to be added to the system keychain, +which is very heavy-handed for a test like this. +`) + } + }() + } + cert := newSelfSignedCert() + defer cert.cleanup() + + s := NewServer() + defer s.Close() + s.EnforceTLS = true + s.Bind = BindAnonOK + s.Search = SearchSimple + s.TLSConfig = cert.ServerTLSConfig() + + ln, addr := mustListen() + go func() { + if err := s.Serve(ln); err != nil { + t.Errorf("s.Serve failed: %s", err.Error()) + } + }() + + done := make(chan struct{}) + go func() { + cmd := exec.Command("env", + "LDAPTLS_CACERT="+cert.CACertPath, + "ldapsearch", "-H", "ldap://"+addr, "-ZZ", "-d", "-1", "-x", "-b", "o=testers,c=test") + out, err := cmd.CombinedOutput() + if err != nil { + t.Error(err) + } + + if !strings.Contains(string(out), "# numEntries: 3") || !strings.Contains(string(out), "result: 0 Success") { + t.Errorf("search did not succeed:\n%s", out) + } + + close(done) + }() + + select { + case <-done: + case <-time.After(timeout): + t.Error("ldapsearch command timed out") + } +} + +func TestEnforcedTLSFail(t *testing.T) { + if runtime.GOOS == "darwin" { + defer func() { + if t.Failed() { + t.Logf(`NOTE: this test won't pass with the built-in Mac ldap utilities. +Work around this by using brew install openldap, and running the test as PATH=/usr/local/opt/openldap/bin:$PATH go test. + +This test uses environment variables that are respected by OpenLDAP, but the Mac utilities don't let you override +security settings through environment variables; they expect certificates to be added to the system keychain, +which is very heavy-handed for a test like this. +`) + } + }() + } + cert := newSelfSignedCert() + defer cert.cleanup() + + s := NewServer() + defer s.Close() + s.EnforceTLS = true + s.Bind = BindAnonOK + s.Search = SearchSimple + s.TLSConfig = cert.ServerTLSConfig() + + ln, addr := mustListen() + go func() { + if err := s.Serve(ln); err != nil { + t.Errorf("s.Serve failed: %s", err.Error()) + } + }() + + done := make(chan struct{}) + go func() { + cmd := exec.Command("env", + "LDAPTLS_CACERT="+cert.CACertPath, + "ldapsearch", "-H", "ldap://"+addr, "-d", "-1", "-x", "-b", "o=testers,c=test") + out, err := cmd.CombinedOutput() + if err == nil { + t.Error("search should have failed") + } + + if strings.Contains(string(out), "result: 0 Success") { + t.Errorf("search did succeed:\n%s", out) + } + + close(done) + }() + + select { + case <-done: + case <-time.After(timeout): + t.Error("ldapsearch command timed out") + } +} + ///////////////////////// func TestBindAnonOK(t *testing.T) { done := make(chan bool) |
