Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/db3f/kafka-proxy into db3…
Browse files Browse the repository at this point in the history
…f-master
  • Loading branch information
everesio committed Oct 11, 2020
2 parents 1bf975d + d33de9b commit 04f8a27
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 16 deletions.
10 changes: 10 additions & 0 deletions cmd/plugin-auth-ldap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,23 @@ build/kafka-proxy server \
--auth-local-enable \
--auth-local-command=build/auth-ldap \
--auth-local-param=--url=ldap://localhost:389 \
--auth-local-param=--ldap-cacerts=/certs/ldap/pem \
--auth-local-param=--start-tls=false \
--auth-local-param=--search-ldap \
--auth-local-param=--bind-dn=cn=admin,dc=example,dc=org \
--auth-local-param=--bind-passwd=admin \
--auth-local-param=--user-search-base=ou=people,dc=example,dc=org \
--auth-local-param=--user-filter="(&(objectClass=person)(uid=%u)(memberOf=cn=kafka-users,ou=realm-roles,dc=example,dc=org))"
```

Setting the flag `--search-ldap` will search the user dn in LDAP, even if `--bind-dn` is not given. This is for LDAP
installations that don't need a bind before allowing readonly actions.(and therefore don't have a readony user)

If `--ldap-cacerts` is set, a (chain of) certificates in PEM format needed to verify the LDAP server's identity
is read from the file given. If the flag ist not set, TLS verification will be skipped



## simple user bind

```
Expand Down
67 changes: 51 additions & 16 deletions cmd/plugin-auth-ldap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"github.com/go-ldap/ldap/v3"
Expand All @@ -10,6 +11,7 @@ import (
"github.com/hashicorp/go-plugin"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"io/ioutil"
"net"
"net/url"
"os"
Expand All @@ -19,13 +21,15 @@ import (
const UsernamePlaceholder = "%u"

type LdapAuthenticator struct {
Urls []string
StartTLS bool
Urls []string
StartTLS bool
TlsConfig *tls.Config

UPNDomain string
UserDN string
UserAttr string

SearchLDAP bool
BindDN string
BindPassword string
UserSearchBase string
Expand Down Expand Up @@ -64,36 +68,39 @@ func (pa LdapAuthenticator) Authenticate(username, password string) (bool, int32

func (pa LdapAuthenticator) getUserBindDN(conn *ldap.Conn, username string) (string, error) {
bindDN := ""
if pa.BindDN != "" {
if pa.SearchLDAP {
var err error
if pa.BindPassword != "" {
err = conn.Bind(pa.BindDN, pa.BindPassword)
} else {
err = conn.UnauthenticatedBind(pa.BindDN)
}
if err != nil {
return "", errors.Wrapf(err, "LDAP bind (service) failed")
if pa.BindDN != "" {
if pa.BindPassword != "" {
err = conn.Bind(pa.BindDN, pa.BindPassword)
} else {
err = conn.UnauthenticatedBind(pa.BindDN)
}
if err != nil {
return "", errors.Wrapf(err, "LDAP bind (service) failed")
}
}
filter := strings.ReplaceAll(pa.UserFilter, UsernamePlaceholder, username)
searchRequest := ldap.NewSearchRequest(
pa.UserSearchBase,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
strings.ReplaceAll(pa.UserFilter, UsernamePlaceholder, username),
filter,
[]string{"dn"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return "", err
return "", errors.Wrapf(err, "base DN %s, filter %s", pa.UserSearchBase, filter)
}
if len(sr.Entries) < 1 {
return "", errors.New("LDAP user search empty result")
return "", errors.Errorf("LDAP user search with base DN %s and filter %s returned empty result", pa.UserSearchBase, filter)
}
if len(sr.Entries) > 1 {
return "", errors.New("LDAP user search not unique result")
return "", errors.Errorf("LDAP user search with base DN %s and filter %s not unique result", pa.UserSearchBase, filter)
}
bindDN = sr.Entries[0].DN
} else {
Expand Down Expand Up @@ -172,7 +179,7 @@ func (pa LdapAuthenticator) DialLDAP() (*ldap.Conn, error) {
if port == "" {
port = "636"
}
conn, err = ldap.DialTLS("tcp", net.JoinHostPort(host, port), &tls.Config{InsecureSkipVerify: true})
conn, err = ldap.DialTLS("tcp", net.JoinHostPort(host, port), pa.TlsConfig)
default:
retErr = multierror.Append(retErr, fmt.Errorf("invalid LDAP scheme in url %q", net.JoinHostPort(host, port)))
continue
Expand All @@ -188,11 +195,13 @@ func (pa LdapAuthenticator) DialLDAP() (*ldap.Conn, error) {

type pluginMeta struct {
url string
cacert string
startTLS bool
upnDomain string
userDN string
userAttr string

searchLDAP bool
bindDN string
bindPassword string
userSearchBase string
Expand All @@ -203,11 +212,13 @@ func (f *pluginMeta) flagSet() *flag.FlagSet {
fs := flag.NewFlagSet("auth plugin settings", flag.ContinueOnError)

fs.StringVar(&f.url, "url", "", "LDAP URL to connect to (eg: ldaps://127.0.0.1:636). Multiple URLs can be specified by concatenating them with commas.")
fs.StringVar(&f.cacert, "ldap-cacert", "", "X509 CA certificate (PEM) to verify peer against")
fs.BoolVar(&f.startTLS, "start-tls", true, "Issue a StartTLS command after establishing unencrypted connection (optional)")
fs.StringVar(&f.upnDomain, "upn-domain", "", "Enables userPrincipalDomain login with [username]@UPNDomain (optional)")
fs.StringVar(&f.userDN, "user-dn", "", "LDAP domain to use for users (eg: cn=users,dc=example,dc=org)")
fs.StringVar(&f.userAttr, "user-attr", "uid", " Attribute used for users")

fs.BoolVar(&f.searchLDAP, "search-ldap", false, "Search LDAP for user DN even if --bind-dn is not set")
fs.StringVar(&f.bindDN, "bind-dn", "", "The Distinguished Name to bind to the LDAP directory to search a user. This can be a readonly or admin user")
fs.StringVar(&f.bindPassword, "bind-passwd", "", "The password used with bindDN")
fs.StringVar(&f.userSearchBase, "user-search-base", "", "The search base as the starting point for the user search e.g. ou=people,dc=example,dc=org")
Expand Down Expand Up @@ -252,7 +263,7 @@ func main() {
logrus.Error(err)
os.Exit(1)
}
if pluginMeta.bindDN != "" {
if pluginMeta.bindDN != "" || pluginMeta.searchLDAP {
logrus.Infof("user-search-base='%s',user-filter='%s'", pluginMeta.userSearchBase, pluginMeta.userFilter)

if pluginMeta.userSearchBase == "" {
Expand All @@ -272,15 +283,23 @@ func main() {
os.Exit(1)
}

tlsConfig, err := getTlsConfig(pluginMeta.cacert)
if err != nil {
logrus.Errorf("error %v getting TLS config", err)
os.Exit(1)
}

plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: shared.Handshake,
Plugins: map[string]plugin.Plugin{
"passwordAuthenticator": &shared.PasswordAuthenticatorPlugin{Impl: &LdapAuthenticator{
Urls: urls,
TlsConfig: tlsConfig,
StartTLS: pluginMeta.startTLS,
UPNDomain: pluginMeta.upnDomain,
UserDN: pluginMeta.userDN,
UserAttr: pluginMeta.userAttr,
SearchLDAP: pluginMeta.searchLDAP || pluginMeta.bindDN != "",
BindDN: pluginMeta.bindDN,
BindPassword: pluginMeta.bindPassword,
UserSearchBase: pluginMeta.userSearchBase,
Expand All @@ -291,3 +310,19 @@ func main() {
GRPCServer: plugin.DefaultGRPCServer,
})
}

func getTlsConfig(caCertFile string) (*tls.Config, error) {
if caCertFile == "" {
return &tls.Config{InsecureSkipVerify: true}, nil
} else {
certData, err := ioutil.ReadFile(caCertFile)
if err != nil {
return nil, errors.Wrapf(err, "reading certificate file %s", caCertFile)
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(certData); !ok {
return nil, errors.Errorf("could not parse certificate(s) in file %s", caCertFile)
}
return &tls.Config{RootCAs: certPool}, nil
}
}

0 comments on commit 04f8a27

Please sign in to comment.