Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
language: go
go:
- 1.7.5
- 1.8.1
- 1.10.x
- 1.11.x
- 1.12.x
script:
- curl -s https://raw.githubusercontent.com/pote/gpm/v1.4.0/bin/gpm > gpm
- chmod +x gpm
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
0.5.0 (2021-02-08)
==================
* Fix support for ldap_scope_name

0.4.0 (2018-11-23)
==================
* URGENT SECURITY FIX: authentication bypass via LDAP passwordless auth LDAP permits passwordless Bind operations by clients - this application verified authentication without checking specifically for an empty password, thus allowing authentication as any valid user by leaving the password field blank. This issue has been present since the first release of this application.

See also:
* https://github.com/go-ldap/ldap/pull/126
* https://github.com/pinepain/ldap-auth-proxy/issues/8
* https://github.com/go-ldap/ldap/issues/93

* Added HTTP security headers and prevent caching of proxy pages

0.3.4 (2018-10-29)
==================
* Make LDAP group comparisons case-insensitive

0.3.3 (2018-06-21)
==================
* Refactor LDAP connection code and use connections more efficiently
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ A reverse proxy and static file server that provides authentication using LDAP.

Strongly inspired by [bitly/oauth2_proxy](https://github.com/bitly/oauth2_proxy).

[![Build Status](https://secure.travis-ci.org/ant1441/ldap_proxy.png?branch=master)](http://travis-ci.org/ant1441/ldap_proxy)
[![Build Status](https://travis-ci.com/skybet/ldap_proxy.svg?branch=master)](https://travis-ci.com/skybet/ldap_proxy)

![Screenshot](docs/screenshot.png)

## Installation

1. Download [Prebuilt Binary](https://github.com/skybet/ldap_proxy/releases) (current release is `v2.2`) or build with `$ go get github.com/skybet/ldap_proxy` which will put the binary in `$GOROOT/bin`
1. Download [Prebuilt Binary](https://github.com/skybet/ldap_proxy/releases) or build with `$ go get github.com/skybet/ldap_proxy` which will put the binary in `$GOROOT/bin`
3. Configure Ldap Proxy using config file, command line options, or environment variables
4. Configure SSL or Deploy behind a SSL endpoint (example provided for Nginx)

Expand Down Expand Up @@ -149,7 +149,7 @@ would be `https://internal.yourcompany.com/`.
An example Nginx config follows. Note the use of `Strict-Transport-Security` header to pin requests to SSL
via [HSTS](http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security):

```
```nginx
server {
listen 443 default ssl;
server_name internal.yourcompany.com;
Expand Down
Binary file modified docs/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 28 additions & 2 deletions http.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (s *Server) ServeHTTP() {
}
log.Printf("HTTP: listening on %s", listenAddr)

server := &http.Server{Handler: s.Handler}
server := &http.Server{Handler: XFrameOptionsMiddleware(s.Handler)}
err = server.Serve(listener)
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
log.Printf("ERROR: http.Serve() - %s", err)
Expand Down Expand Up @@ -83,7 +83,7 @@ func (s *Server) ServeHTTPS() {
log.Printf("HTTPS: listening on %s", ln.Addr())

tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config)
srv := &http.Server{Handler: s.Handler}
srv := &http.Server{Handler: HSTSMiddleware(XFrameOptionsMiddleware(s.Handler))}
err = srv.Serve(tlsListener)

if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
Expand All @@ -110,3 +110,29 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}

// HSTSMiddleware sets Strict-Transport-Security header
func HSTSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
next.ServeHTTP(w, r)
})
}

// XFrameOptionsMiddleware sets X-Frame-Options header
func XFrameOptionsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("X-Frame-Options", "deny")
next.ServeHTTP(w, r)
})
}

// NoCache sets no cache headers
func NoCache(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Cache-control", "no-store")
w.Header().Add("Pragma", "no-cache")

next(w, r)
}
}
5 changes: 4 additions & 1 deletion ldap_connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ func (c *LDAPClient) Close() {

// Authenticate authenticates the user against the ldap backend.
func (c *LDAPClient) Authenticate(username, password string) (bool, map[string]string, error) {
if username == "" || password == "" {
return false, nil, errors.New("invalid user or password")
}

// First bind with a read only user
if c.cfg.BindDN != "" && c.cfg.BindPassword != "" {
Expand Down Expand Up @@ -116,7 +119,7 @@ func (c *LDAPClient) Authenticate(username, password string) (bool, map[string]s
if c.cfg.BindDN != "" && c.cfg.BindPassword != "" {
err = c.conn.Bind(c.cfg.BindDN, c.cfg.BindPassword)
if err != nil {
return true, user, err
return false, user, err
}
}

Expand Down
25 changes: 14 additions & 11 deletions ldap_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ type LdapProxy struct {
AuthOnlyPath string

ProxyPrefix string
LdapScopeName string
SignInMessage string
HtpasswdFile *HtpasswdFile
serveMux http.Handler
Expand Down Expand Up @@ -175,6 +176,7 @@ func NewLdapProxy(opts *Options, validator func(string) bool) *LdapProxy {

LdapConfiguration: ldapCfg,
LdapGroups: opts.LdapGroups,
LdapScopeName: opts.LdapScopeName,

skipAuthRegex: opts.SkipAuthRegex,
skipAuthIPs: opts.skipIPs,
Expand Down Expand Up @@ -220,12 +222,12 @@ func (p *LdapProxy) makeCookie(req *http.Request, name string, value string, exp
}
}

func (p *LdapProxy) RobotsTxt(rw http.ResponseWriter) {
func (p *LdapProxy) RobotsTxt(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
}

func (p *LdapProxy) PingPage(rw http.ResponseWriter) {
func (p *LdapProxy) PingPage(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
fmt.Fprintf(rw, "OK")
}
Expand Down Expand Up @@ -267,6 +269,7 @@ func (p *LdapProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code i
ProxyPrefix string
Footer template.HTML
}{
LdapScopeName: p.LdapScopeName,
SignInMessage: p.SignInMessage,
Failed: failed,
Redirect: redirectURL,
Expand Down Expand Up @@ -396,17 +399,17 @@ func (p *LdapProxy) getRemoteAddrStr(req *http.Request) (s string) {
func (p *LdapProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
switch path := req.URL.Path; {
case path == p.RobotsPath:
p.RobotsTxt(rw)
NoCache(p.RobotsTxt)(rw, req)
case path == p.PingPath:
p.PingPage(rw)
NoCache(p.PingPage)(rw, req)
case p.IsWhitelistedRequest(req):
p.serveMux.ServeHTTP(rw, req)
case path == p.SignInPath:
p.SignIn(rw, req)
NoCache(p.SignIn)(rw, req)
case path == p.SignOutPath:
p.SignOut(rw, req)
NoCache(p.SignOut)(rw, req)
case path == p.AuthOnlyPath:
p.AuthenticateOnly(rw, req)
NoCache(p.AuthenticateOnly)(rw, req)
default:
p.Proxy(rw, req)
}
Expand Down Expand Up @@ -464,7 +467,7 @@ func (p *LdapProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
}

if len(p.LdapGroups) > 0 {
if sliceContains(p.LdapGroups, groups) {
if sliceContainsString(p.LdapGroups, groups) {
if err := p.SaveSession(rw, req, session); err != nil {
log.Printf("failed to save session %v", err)
}
Expand Down Expand Up @@ -637,11 +640,11 @@ func (p *LdapProxy) CheckBasicAuth(req *http.Request) (*SessionState, error) {
return nil, fmt.Errorf("%s not in HtpasswdFile", pair[0])
}

// sliceContains returns true if a and b contains any common string
func sliceContains(a, b []string) bool {
// sliceContainsString returns true if a and b contains any common string ignoring case
func sliceContainsString(a, b []string) bool {
for _, aItem := range a {
for _, bItem := range b {
if aItem == bItem {
if strings.ToLower(aItem) == strings.ToLower(bItem) {
return true
}
}
Expand Down
10 changes: 8 additions & 2 deletions ldap_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ func (fnc *fakeNetConn) Read(p []byte) (n int, err error) {
return 0, io.EOF
}

func TestSliceContains(t *testing.T) {
func TestSliceContainsString(t *testing.T) {
testCases := []struct {
desc string
a []string
Expand All @@ -133,6 +133,12 @@ func TestSliceContains(t *testing.T) {
b: []string{"b"},
expect: true,
},
{
desc: "happy path case insensitive",
a: []string{"a", "B", "c"},
b: []string{"b"},
expect: true,
},
{
desc: "empty",
a: []string{},
Expand All @@ -149,7 +155,7 @@ func TestSliceContains(t *testing.T) {

for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
if res := sliceContains(tC.a, tC.b); res != tC.expect {
if res := sliceContainsString(tC.a, tC.b); res != tC.expect {
t.Errorf("with a %+v and b %+v, expected %+v, got %v", tC.a, tC.b, tC.expect, res)
}
})
Expand Down
2 changes: 1 addition & 1 deletion templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func getTemplates() *template.Template {
{{ if .SignInMessage }}
<p>{{.SignInMessage}}</p>
{{ end}}
<h1>Sign in with a {{.LdapScopeName}} Account<br/></h1>
<h1>Sign in with your {{.LdapScopeName}} account<br/></h1>
</div>

{{ if .Failed }}
Expand Down
2 changes: 1 addition & 1 deletion version.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main

// VERSION released
const VERSION = "0.3.3"
const VERSION = "0.5.0"