aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--messages.go3
-rw-r--r--server.go31
-rw-r--r--server_test.go135
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"
diff --git a/server.go b/server.go
index bc7848d..5bad5ff 100644
--- a/server.go
+++ b/server.go
@@ -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)