Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional group/ldap-ou check #7

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
3 changes: 2 additions & 1 deletion auth/token_issuer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type LDAPTokenIssuer struct {
LDAPServer string
LDAPAuthenticator ldap.Authenticator
TokenSigner token.Signer
LDAPOU string
}

func (lti *LDAPTokenIssuer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
Expand All @@ -26,7 +27,7 @@ func (lti *LDAPTokenIssuer) ServeHTTP(resp http.ResponseWriter, req *http.Reques
}

// Authenticate the user via LDAP
ldapEntry, err := lti.LDAPAuthenticator.Authenticate(user, password)
ldapEntry, err := lti.LDAPAuthenticator.Authenticate(user, password, lti.LDAPOU)
if err != nil {
glog.Errorf("Error authenticating user: %v", err)
resp.WriteHeader(http.StatusUnauthorized)
Expand Down
2 changes: 1 addition & 1 deletion auth/token_issuer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type dummyLDAP struct {
err error
}

func (d dummyLDAP) Authenticate(username, password string) (*ldap.Entry, error) {
func (d dummyLDAP) Authenticate(username, password, ldapOU string) (*ldap.Entry, error) {
return d.entry, d.err
}

Expand Down
14 changes: 10 additions & 4 deletions auth/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ import (
// TokenWebhook responds to requests from the K8s authentication webhook
type TokenWebhook struct {
tokenVerifier token.Verifier
ldapOU string
}

// NewTokenWebhook returns a TokenWebhook with the given verifier
func NewTokenWebhook(verifier token.Verifier) *TokenWebhook {
func NewTokenWebhook(verifier token.Verifier, ldapOU string) *TokenWebhook {
return &TokenWebhook{
tokenVerifier: verifier,
ldapOU: ldapOU,
}
}

Expand Down Expand Up @@ -46,11 +48,15 @@ func (tw *TokenWebhook) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
}

// Token is valid.
userInfo := UserInfo{
Username: token.Username,
}
if tw.ldapOU != "" {
userInfo.Groups = []string{tw.ldapOU}
}
trr.Status = TokenReviewStatus{
Authenticated: true,
User: UserInfo{
Username: token.Username,
},
User: userInfo,
}

respJSON, err := json.Marshal(trr)
Expand Down
2 changes: 1 addition & 1 deletion auth/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func TestWebhook(t *testing.T) {

for i, c := range cases {
v := &dummyVerifier{token: c.verifiedToken, err: c.verifyErr}
tw := NewTokenWebhook(v)
tw := NewTokenWebhook(v, "")

trr := &TokenReviewRequest{
Spec: TokenReviewSpec{
Expand Down
4 changes: 3 additions & 1 deletion cmd/kubernetes-ldap.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ var flLdapAllowInsecure = flag.Bool("ldap-insecure", false, "Disable LDAP TLS")
var flLdapHost = flag.String("ldap-host", "", "Host or IP of the LDAP server")
var flLdapPort = flag.Uint("ldap-port", 389, "LDAP server port")
var flBaseDN = flag.String("ldap-base-dn", "", "LDAP user base DN in the form 'dc=example,dc=com'")
var flLdapOU = flag.String("ldap-ou", "", "LDAP group/organizational unit (ou) a user must be member of")
var flUserLoginAttribute = flag.String("ldap-user-attribute", "uid", "LDAP Username attribute for login")
var flSearchUserDN = flag.String("ldap-search-user-dn", "", "Search user DN for this app to find users (e.g.: cn=admin,dc=example,dc=com).")
var flSearchUserPassword = flag.String("ldap-search-user-password", "", "Search user password")
Expand Down Expand Up @@ -87,11 +88,12 @@ func main() {

server := &http.Server{Addr: fmt.Sprintf(":%d", *flServerPort)}

webhook := auth.NewTokenWebhook(tokenVerifier)
webhook := auth.NewTokenWebhook(tokenVerifier, *flLdapOU)

ldapTokenIssuer := &auth.LDAPTokenIssuer{
LDAPAuthenticator: ldapClient,
TokenSigner: tokenSigner,
LDAPOU: *flLdapOU,
}

// Endpoint for authenticating with token
Expand Down
35 changes: 33 additions & 2 deletions ldap/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"crypto/tls"
"errors"
"fmt"
"strings"

"github.com/go-ldap/ldap"
)

// Authenticator authenticates a user against an LDAP directory
type Authenticator interface {
Authenticate(username, password string) (*ldap.Entry, error)
Authenticate(username, password, ldapOU string) (*ldap.Entry, error)
}

// Client represents a connection, and associated lookup strategy,
Expand All @@ -28,7 +29,7 @@ type Client struct {

// Authenticate a user against the LDAP directory. Returns an LDAP entry if password
// is valid, otherwise returns an error.
func (c *Client) Authenticate(username, password string) (*ldap.Entry, error) {
func (c *Client) Authenticate(username, password, ldapOU string) (*ldap.Entry, error) {
conn, err := c.dial()
if err != nil {
return nil, fmt.Errorf("Error opening LDAP connection: %v", err)
Expand Down Expand Up @@ -60,6 +61,13 @@ func (c *Client) Authenticate(username, password string) (*ldap.Entry, error) {
return nil, fmt.Errorf("Multiple entries found for the search filter '%s': %+v", req.Filter, res.Entries)
}

if ldapOU != "" {
err = c.isInLdapOU(conn, username, ldapOU)
if err != nil {
return nil, fmt.Errorf("Organizational unit check failed: %s", err)
}
}

// Now that we know the user exists within the BaseDN scope
// let's do user bind to check credentials using the full DN instead of
// the attribute used for search
Expand Down Expand Up @@ -105,3 +113,26 @@ func (c *Client) newUserSearchRequest(username string) *ldap.SearchRequest {
Filter: userFilter,
}
}

func (c *Client) isInLdapOU(conn *ldap.Conn, username, ldapOU string) error {
req := &ldap.SearchRequest{
BaseDN: c.BaseDN,
Scope: ldap.ScopeWholeSubtree,
Filter: fmt.Sprintf("(memberUid=%s)", username),
}

res, err := conn.Search(req)
if err != nil {
return fmt.Errorf("Error searching for user %s: %v", username, err)
}
if len(res.Entries) == 0 {
return fmt.Errorf("Received an empty response")
}
for _, entry := range res.Entries {
if strings.Contains(entry.DN, "cn="+ldapOU) {
return nil
}
}

return fmt.Errorf("%q is not a member of %q", username, ldapOU)
}