[ARVADOS] created: 1.3.0-2542-g9621437cd

Git user git at public.arvados.org
Tue May 5 21:11:01 UTC 2020


        at  9621437cdc4ae4dff6fca5a53d85700d5f61a227 (commit)


commit 9621437cdc4ae4dff6fca5a53d85700d5f61a227
Author: Tom Clegg <tom at tomclegg.ca>
Date:   Tue May 5 17:10:53 2020 -0400

    15881: Add LDAP authentication option.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom at tomclegg.ca>

diff --git a/go.mod b/go.mod
index 34b7e0779..482c6971d 100644
--- a/go.mod
+++ b/go.mod
@@ -25,6 +25,7 @@ require (
 	github.com/fsnotify/fsnotify v1.4.9
 	github.com/ghodss/yaml v1.0.0
 	github.com/gliderlabs/ssh v0.2.2 // indirect
+	github.com/go-ldap/ldap v3.0.3+incompatible
 	github.com/gogo/protobuf v1.1.1
 	github.com/gorilla/context v1.1.1 // indirect
 	github.com/gorilla/mux v1.6.1-0.20180107155708-5bbbb5b2b572
@@ -57,6 +58,7 @@ require (
 	golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
 	golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd
 	google.golang.org/api v0.13.0
+	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
 	gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405
 	gopkg.in/square/go-jose.v2 v2.3.1
 	gopkg.in/src-d/go-billy.v4 v4.0.1
diff --git a/go.sum b/go.sum
index 03b2f77b6..a92b3c11a 100644
--- a/go.sum
+++ b/go.sum
@@ -64,6 +64,8 @@ github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
 github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
+github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -249,6 +251,8 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
 google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405 h1:829vOVxxusYHC+IqBtkX5mbKtsY9fheQiQn0MZRVLfQ=
 gopkg.in/check.v1 v1.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/lib/config/config.default.yml b/lib/config/config.default.yml
index d4870919e..b866c7491 100644
--- a/lib/config/config.default.yml
+++ b/lib/config/config.default.yml
@@ -573,6 +573,76 @@ Clusters:
       # accounts.
       PAMDefaultEmailDomain: ""
 
+      LDAP:
+        # Use an LDAP service to authenticate users.
+        Enable: true
+
+        # Server URL, like "ldap://ldapserver.example.com:389".
+        #
+        # If the server requires authentication before looking up and
+        # authenticating users, embed the credentials in the URL:
+        # "ldap://username:password@ldapserver.example.com:389"
+        URL: "ldap://ldap:389"
+
+        # Use StartTLS upon connecting to the server.
+        StartTLS: true
+
+        # Skip TLS certificate name verification.
+        InsecureTLS: false
+
+        # Strip the @domain part if a user supplies an email-style
+        # username with this domain. If "*", strip any user-provided
+        # domain. If "", never strip the domain part. Example:
+        # "example.com"
+        StripDomain: ""
+
+        # If, after applying StripDomain, the username contains no "@"
+        # character, append this domain to form an email-style
+        # username. When using an Active Directory service, this
+        # typically matches LookupBase. Example: "example.com"
+        AppendDomain: ""
+
+        # Resolve the supplied username (after applying StripDomain
+        # and AppendDomain) to a DN, filtering on this attribute. If
+        # empty, skip the lookup, and just authenticate using the
+        # supplied username as the DN. Example: "uid"
+        LookupUsernameAttribute: ""
+
+        # Directory base for username lookup, e.g.,
+        # "ou=Users, dc=example, dc=com"
+        LookupBase: ""
+
+        # Additional filters for username lookup. Special characters
+        # in assertion values must be escaped (see RFC4515). Example:
+        # "(objectClass=person)"
+        LookupExtraFilters: ""
+
+        # Space-delimited ordered list of LDAP attributes to search
+        # for the user's email address. If this list is empty or none
+        # of the attributes are provided by the LDAP server,
+        # EmailDomain is used instead (see below).
+        #
+        # Important: This list must not contain any attributes whose
+        # values can be edited in the directory by the users
+        # themselves. Otherwise, users can take over other users'
+        # Arvados accounts trivially.
+        #
+        # Example: "mail"
+        EmailAttributes: ""
+
+        # Domain name to use to construct the user's email address if
+        # the user's DN does not contain "@". If empty, use the DN as
+        # the email address whether or not it contains "@".
+        #
+        # Note that the email address is used as the primary key for
+        # user records when logging in. Therefore, if you change
+        # EmailDomain after the initial installation, you should also
+        # update existing user records to reflect the new
+        # domain. Otherwise, next time those users log in, they will
+        # be given new accounts instead of accessing their existing
+        # accounts.
+        EmailDomain: ""
+
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
       # (login cluster must appear in RemoteClusters with Proxy: true)
diff --git a/lib/config/export.go b/lib/config/export.go
index ded03fc30..85a8c6a44 100644
--- a/lib/config/export.go
+++ b/lib/config/export.go
@@ -139,6 +139,18 @@ var whitelist = map[string]bool{
 	"Login.PAMDefaultEmailDomain":                  false,
 	"Login.ProviderAppID":                          false,
 	"Login.ProviderAppSecret":                      false,
+	"Login.LDAP":                                   true,
+	"Login.LDAP.AppendDomain":                      false,
+	"Login.LDAP.EmailAttributes":                   false,
+	"Login.LDAP.EmailDomain":                       false,
+	"Login.LDAP.Enable":                            true,
+	"Login.LDAP.InsecureTLS":                       false,
+	"Login.LDAP.LookupBase":                        false,
+	"Login.LDAP.LookupExtraFilters":                false,
+	"Login.LDAP.LookupUsernameAttribute":           false,
+	"Login.LDAP.StartTLS":                          false,
+	"Login.LDAP.StripDomain":                       false,
+	"Login.LDAP.URL":                               false,
 	"Login.LoginCluster":                           true,
 	"Login.RemoteTokenRefresh":                     true,
 	"Mail":                                         true,
diff --git a/lib/config/generated_config.go b/lib/config/generated_config.go
index 42707396d..1eb01fecb 100644
--- a/lib/config/generated_config.go
+++ b/lib/config/generated_config.go
@@ -579,6 +579,76 @@ Clusters:
       # accounts.
       PAMDefaultEmailDomain: ""
 
+      LDAP:
+        # Use an LDAP service to authenticate users.
+        Enable: true
+
+        # Server URL, like "ldap://ldapserver.example.com:389".
+        #
+        # If the server requires authentication before looking up and
+        # authenticating users, embed the credentials in the URL:
+        # "ldap://username:password@ldapserver.example.com:389"
+        URL: "ldap://ldap:389"
+
+        # Use StartTLS upon connecting to the server.
+        StartTLS: true
+
+        # Skip TLS certificate name verification.
+        InsecureTLS: false
+
+        # Strip the @domain part if a user supplies an email-style
+        # username with this domain. If "*", strip any user-provided
+        # domain. If "", never strip the domain part. Example:
+        # "example.com"
+        StripDomain: ""
+
+        # If, after applying StripDomain, the username contains no "@"
+        # character, append this domain to form an email-style
+        # username. When using an Active Directory service, this
+        # typically matches LookupBase. Example: "example.com"
+        AppendDomain: ""
+
+        # Resolve the supplied username (after applying StripDomain
+        # and AppendDomain) to a DN, filtering on this attribute. If
+        # empty, skip the lookup, and just authenticate using the
+        # supplied username as the DN. Example: "uid"
+        LookupUsernameAttribute: ""
+
+        # Directory base for username lookup, e.g.,
+        # "ou=Users, dc=example, dc=com"
+        LookupBase: ""
+
+        # Additional filters for username lookup. Special characters
+        # in assertion values must be escaped (see RFC4515). Example:
+        # "(objectClass=person)"
+        LookupExtraFilters: ""
+
+        # Space-delimited ordered list of LDAP attributes to search
+        # for the user's email address. If this list is empty or none
+        # of the attributes are provided by the LDAP server,
+        # EmailDomain is used instead (see below).
+        #
+        # Important: This list must not contain any attributes whose
+        # values can be edited in the directory by the users
+        # themselves. Otherwise, users can take over other users'
+        # Arvados accounts trivially.
+        #
+        # Example: "mail"
+        EmailAttributes: ""
+
+        # Domain name to use to construct the user's email address if
+        # the user's DN does not contain "@". If empty, use the DN as
+        # the email address whether or not it contains "@".
+        #
+        # Note that the email address is used as the primary key for
+        # user records when logging in. Therefore, if you change
+        # EmailDomain after the initial installation, you should also
+        # update existing user records to reflect the new
+        # domain. Otherwise, next time those users log in, they will
+        # be given new accounts instead of accessing their existing
+        # accounts.
+        EmailDomain: ""
+
       # The cluster ID to delegate the user database.  When set,
       # logins on this cluster will be redirected to the login cluster
       # (login cluster must appear in RemoteClusters with Proxy: true)
diff --git a/lib/controller/localdb/login.go b/lib/controller/localdb/login.go
index ae5984999..8cba3b6fa 100644
--- a/lib/controller/localdb/login.go
+++ b/lib/controller/localdb/login.go
@@ -8,8 +8,11 @@ import (
 	"context"
 	"errors"
 	"net/http"
+	"net/url"
 
+	"git.arvados.org/arvados.git/lib/controller/rpc"
 	"git.arvados.org/arvados.git/sdk/go/arvados"
+	"git.arvados.org/arvados.git/sdk/go/auth"
 	"git.arvados.org/arvados.git/sdk/go/httpserver"
 )
 
@@ -23,16 +26,19 @@ func chooseLoginController(cluster *arvados.Cluster, railsProxy *railsProxy) log
 	wantGoogle := cluster.Login.GoogleClientID != ""
 	wantSSO := cluster.Login.ProviderAppID != ""
 	wantPAM := cluster.Login.PAM
+	wantLDAP := cluster.Login.LDAP.Enable
 	switch {
-	case wantGoogle && !wantSSO && !wantPAM:
+	case wantGoogle && !wantSSO && !wantPAM && !wantLDAP:
 		return &googleLoginController{Cluster: cluster, RailsProxy: railsProxy}
-	case !wantGoogle && wantSSO && !wantPAM:
+	case !wantGoogle && wantSSO && !wantPAM && !wantLDAP:
 		return &ssoLoginController{railsProxy}
-	case !wantGoogle && !wantSSO && wantPAM:
+	case !wantGoogle && !wantSSO && wantPAM && !wantLDAP:
 		return &pamLoginController{Cluster: cluster, RailsProxy: railsProxy}
+	case !wantGoogle && !wantSSO && !wantPAM && wantLDAP:
+		return &ldapLoginController{Cluster: cluster, RailsProxy: railsProxy}
 	default:
 		return errorLoginController{
-			error: errors.New("configuration problem: exactly one of Login.GoogleClientID, Login.ProviderAppID, or Login.PAM must be configured"),
+			error: errors.New("configuration problem: exactly one of Login.GoogleClientID, Login.ProviderAppID, Login.PAM, or Login.LDAP.Enable must be configured"),
 		}
 	}
 }
@@ -68,3 +74,23 @@ func noopLogout(cluster *arvados.Cluster, opts arvados.LogoutOptions) (arvados.L
 	}
 	return arvados.LogoutResponse{RedirectLocation: target}, nil
 }
+
+func createAPIClientAuthorization(ctx context.Context, conn *rpc.Conn, rootToken string, authinfo rpc.UserSessionAuthInfo) (arvados.APIClientAuthorization, error) {
+	ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{rootToken}})
+	resp, err := conn.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
+		// Send a fake ReturnTo value instead of the caller's
+		// opts.ReturnTo. We won't follow the resulting
+		// redirect target anyway.
+		ReturnTo: ",https://none.invalid",
+		AuthInfo: authinfo,
+	})
+	if err != nil {
+		return arvados.APIClientAuthorization{}, err
+	}
+	target, err := url.Parse(resp.RedirectLocation)
+	if err != nil {
+		return arvados.APIClientAuthorization{}, err
+	}
+	token := target.Query().Get("api_token")
+	return conn.APIClientAuthorizationCurrent(auth.NewContext(ctx, auth.NewCredentials(token)), arvados.GetOptions{})
+}
diff --git a/lib/controller/localdb/login_ldap.go b/lib/controller/localdb/login_ldap.go
new file mode 100644
index 000000000..dc46e3471
--- /dev/null
+++ b/lib/controller/localdb/login_ldap.go
@@ -0,0 +1,159 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package localdb
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
+	"strings"
+
+	"git.arvados.org/arvados.git/lib/controller/rpc"
+	"git.arvados.org/arvados.git/sdk/go/arvados"
+	"git.arvados.org/arvados.git/sdk/go/ctxlog"
+	"git.arvados.org/arvados.git/sdk/go/httpserver"
+	"github.com/go-ldap/ldap"
+	"github.com/sirupsen/logrus"
+)
+
+var errAuthFailed = httpserver.ErrorWithStatus(errors.New("authentication failed"), http.StatusUnauthorized)
+
+type ldapLoginController struct {
+	Cluster    *arvados.Cluster
+	RailsProxy *railsProxy
+}
+
+func (ctrl *ldapLoginController) Logout(ctx context.Context, opts arvados.LogoutOptions) (arvados.LogoutResponse, error) {
+	return noopLogout(ctrl.Cluster, opts)
+}
+
+func (ctrl *ldapLoginController) Login(ctx context.Context, opts arvados.LoginOptions) (arvados.LoginResponse, error) {
+	return arvados.LoginResponse{}, errors.New("interactive login is not available")
+}
+
+func (ctrl *ldapLoginController) UserAuthenticate(ctx context.Context, opts arvados.UserAuthenticateOptions) (arvados.APIClientAuthorization, error) {
+	log := ctxlog.FromContext(ctx)
+	conf := ctrl.Cluster.Login.LDAP
+
+	if opts.Password == "" {
+		log.WithField("username", opts.Username).Error("refusing to authenticate with empty password")
+		return arvados.APIClientAuthorization{}, errAuthFailed
+	}
+
+	ldapurl := conf.URL
+	ldapurl.User = nil
+	log = log.WithField("URL", ldapurl.String())
+	l, err := ldap.DialURL(ldapurl.String())
+	if err != nil {
+		log.WithError(err).Error("ldap connection failed")
+		return arvados.APIClientAuthorization{}, err
+	}
+	defer l.Close()
+
+	if conf.StartTLS {
+		var tlsconfig tls.Config
+		if conf.InsecureTLS {
+			tlsconfig.InsecureSkipVerify = true
+		} else {
+			if host, _, err := net.SplitHostPort(ldapurl.Host); err != nil {
+				// Assume SplitHostPort error means
+				// port was not specified
+				tlsconfig.ServerName = ldapurl.Host
+			} else {
+				tlsconfig.ServerName = host
+			}
+		}
+		err = l.StartTLS(&tlsconfig)
+		if err != nil {
+			log.WithError(err).Error("ldap starttls failed")
+			return arvados.APIClientAuthorization{}, err
+		}
+	}
+
+	if pass, isSet := conf.URL.User.Password(); isSet {
+		err = l.Bind(conf.URL.User.Username(), pass)
+		if err != nil {
+			log.WithError(err).WithField("user", conf.URL.User.Username()).Error("ldap lookup authentication failed")
+			return arvados.APIClientAuthorization{}, err
+		}
+	}
+
+	username := opts.Username
+	if at := strings.Index(username, "@"); at >= 0 {
+		if conf.StripDomain == "*" || strings.ToLower(conf.StripDomain) == strings.ToLower(username[at+1:]) {
+			username = username[:at]
+		}
+	}
+	if conf.AppendDomain != "" && !strings.Contains(username, "@") {
+		username = username + "@" + conf.AppendDomain
+	}
+
+	var userdn string
+	attrs := map[string]string{}
+	if conf.LookupUsernameAttribute == "" {
+		userdn = username
+	} else {
+		search := fmt.Sprintf("(&%s(%s=%s))", conf.LookupExtraFilters, ldap.EscapeFilter(conf.LookupUsernameAttribute), ldap.EscapeFilter(username))
+		req := ldap.NewSearchRequest(
+			conf.LookupBase,
+			ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false,
+			search,
+			[]string{"dn"}, nil)
+		resp, err := l.Search(req)
+		if err != nil {
+			log.WithField("search", search).WithError(err).Debug("ldap lookup failed")
+			return arvados.APIClientAuthorization{}, err
+		}
+		if len(resp.Entries) == 0 {
+			ctxlog.FromContext(ctx).WithField("search", search).Debug("ldap lookup returned 0 entries")
+			return arvados.APIClientAuthorization{}, errAuthFailed
+		}
+		userdn = resp.Entries[0].DN
+		for _, attr := range resp.Entries[0].Attributes {
+			if attr == nil || len(attr.Values) == 0 {
+				continue
+			}
+			attrs[attr.Name] = attr.Values[0]
+		}
+		ctxlog.FromContext(ctx).WithField("dn", userdn).WithField("attrs", attrs).Debug("ldap lookup succeeded")
+	}
+
+	if userdn == "" {
+		log.Warn("refusing to authenticate with empty dn")
+		return arvados.APIClientAuthorization{}, errAuthFailed
+	}
+	err = l.Bind(userdn, opts.Password)
+	if err != nil {
+		log.WithError(err).WithField("dn", userdn).Warn("ldap user authentication failed")
+		return arvados.APIClientAuthorization{}, errAuthFailed
+	}
+	ctxlog.FromContext(ctx).WithFields(logrus.Fields{
+		"usernameSupplied": opts.Username,
+		"usernameLookup":   username,
+		"dn":               userdn,
+	}).Debug("ldap authentication succeeded")
+
+	email := ""
+	for _, key := range strings.Split(conf.EmailAttributes, " ") {
+		if key != "" && attrs[key] != "" {
+			email = attrs[key]
+			break
+		}
+	}
+	if email != "" {
+	} else if strings.Contains(username, "@") {
+		email = username
+	} else {
+		email = username + "@" + conf.AppendDomain
+	}
+	return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+		Email:     email,
+		FirstName: attrs["givenName"],
+		LastName:  attrs["SN"],
+	})
+}
diff --git a/lib/controller/localdb/login_pam_docker_test.go b/lib/controller/localdb/login_ldap_docker_test.go
similarity index 52%
rename from lib/controller/localdb/login_pam_docker_test.go
rename to lib/controller/localdb/login_ldap_docker_test.go
index 8a02b2c38..9eb93d5aa 100644
--- a/lib/controller/localdb/login_pam_docker_test.go
+++ b/lib/controller/localdb/login_ldap_docker_test.go
@@ -15,9 +15,19 @@ import (
 )
 
 func (s *PamSuite) TestLoginLDAPViaPAM(c *check.C) {
-	cmd := exec.Command("bash", "login_pam_docker_test.sh")
+	cmd := exec.Command("bash", "login_ldap_docker_test.sh")
 	cmd.Stdout = os.Stderr
 	cmd.Stderr = os.Stderr
+	cmd.Env = append(os.Environ(), "config_method=pam")
+	err := cmd.Run()
+	c.Check(err, check.IsNil)
+}
+
+func (s *PamSuite) TestLoginLDAPBuiltin(c *check.C) {
+	cmd := exec.Command("bash", "login_ldap_docker_test.sh")
+	cmd.Stdout = os.Stderr
+	cmd.Stderr = os.Stderr
+	cmd.Env = append(os.Environ(), "config_method=ldap")
 	err := cmd.Run()
 	c.Check(err, check.IsNil)
 }
diff --git a/lib/controller/localdb/login_pam_docker_test.sh b/lib/controller/localdb/login_ldap_docker_test.sh
similarity index 86%
rename from lib/controller/localdb/login_pam_docker_test.sh
rename to lib/controller/localdb/login_ldap_docker_test.sh
index b8f281bc2..b6f51e2bc 100755
--- a/lib/controller/localdb/login_pam_docker_test.sh
+++ b/lib/controller/localdb/login_ldap_docker_test.sh
@@ -2,9 +2,9 @@
 
 # This script demonstrates using LDAP for Arvados user authentication.
 #
-# It configures pam_ldap(5) and arvados controller in a docker
-# container, with pam_ldap configured to authenticate against an
-# OpenLDAP server in a second docker container.
+# It configures arvados controller in a docker container, optionally
+# with pam_ldap(5) configured to authenticate against an OpenLDAP
+# server in a second docker container.
 #
 # After adding a "foo" user entry, it uses curl to check that the
 # Arvados controller's login endpoint accepts the "foo" account
@@ -19,11 +19,20 @@
 set -e -o pipefail
 
 debug=/dev/null
-if [[ -n ${ARVADOS_DEBUG} ]]; then
+if true || [[ -n ${ARVADOS_DEBUG} ]]; then
     debug=/dev/stderr
     set -x
 fi
 
+case "${config_method}" in
+    pam | ldap)
+        ;;
+    *)
+        echo >&2 "\$config_method env var must be 'pam' or 'ldap'"
+        exit 1
+        ;;
+esac
+
 hostname="$(hostname)"
 tmpdir="$(mktemp -d)"
 cleanup() {
@@ -86,15 +95,32 @@ Clusters:
         ExternalURL: http://0.0.0.0:9999/
         InternalURLs:
           "http://0.0.0.0:9999/": {}
+    SystemLogs:
+      LogLevel: debug
+EOF
+case "${config_method}" in
+    pam)
+        setup_pam_ldap="apt update && DEBIAN_FRONTEND=noninteractive apt install -y ldap-utils libpam-ldap && pam-auth-update --package /usr/share/pam-configs/ldap"
+        cat >>"${tmpdir}/zzzzz.yml" <<EOF
     Login:
       PAM: true
       # Without this magic PAMDefaultEmailDomain, inserted users would
       # prevent subsequent database/reset from working (see
       # database_controller.rb).
       PAMDefaultEmailDomain: example.com
-    SystemLogs:
-      LogLevel: debug
 EOF
+        ;;
+    ldap)
+        setup_pam_ldap=""
+        cat >>"${tmpdir}/zzzzz.yml" <<EOF
+    Login:
+      LDAP:
+        Enable: true
+        URL: ${ldapurl}
+        StartTLS: false
+EOF
+            ;;
+esac
 
 cat >"${tmpdir}/pam_ldap.conf" <<EOF
 base dc=example,dc=org
@@ -152,7 +178,7 @@ docker run --detach --rm --name=${ctrlctr} \
        -v "${tmpdir}/zzzzz.yml":/etc/arvados/config.yml:ro \
        -v $(realpath "${PWD}/../../.."):/arvados:ro \
        debian:10 \
-       bash -c "apt update && DEBIAN_FRONTEND=noninteractive apt install -y ldap-utils libpam-ldap && pam-auth-update --package /usr/share/pam-configs/ldap && arvados-server controller"
+       bash -c "${setup_pam_ldap:-true} && arvados-server controller"
 docker logs --follow ${ctrlctr} 2>$debug >$debug &
 ctrlhostport=$(docker port ${ctrlctr} 9999/tcp)
 
diff --git a/lib/controller/localdb/login_pam.go b/lib/controller/localdb/login_pam.go
index 01dfc1379..538e3118e 100644
--- a/lib/controller/localdb/login_pam.go
+++ b/lib/controller/localdb/login_pam.go
@@ -9,12 +9,10 @@ import (
 	"errors"
 	"fmt"
 	"net/http"
-	"net/url"
 	"strings"
 
 	"git.arvados.org/arvados.git/lib/controller/rpc"
 	"git.arvados.org/arvados.git/sdk/go/arvados"
-	"git.arvados.org/arvados.git/sdk/go/auth"
 	"git.arvados.org/arvados.git/sdk/go/ctxlog"
 	"git.arvados.org/arvados.git/sdk/go/httpserver"
 	"github.com/msteinert/pam"
@@ -85,25 +83,12 @@ func (ctrl *pamLoginController) UserAuthenticate(ctx context.Context, opts arvad
 	if domain := ctrl.Cluster.Login.PAMDefaultEmailDomain; domain != "" && !strings.Contains(email, "@") {
 		email = email + "@" + domain
 	}
-	ctxlog.FromContext(ctx).WithFields(logrus.Fields{"user": user, "email": email}).Debug("pam authentication succeeded")
-	ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{ctrl.Cluster.SystemRootToken}})
-	resp, err := ctrl.RailsProxy.UserSessionCreate(ctxRoot, rpc.UserSessionCreateOptions{
-		// Send a fake ReturnTo value instead of the caller's
-		// opts.ReturnTo. We won't follow the resulting
-		// redirect target anyway.
-		ReturnTo: ",https://none.invalid",
-		AuthInfo: rpc.UserSessionAuthInfo{
-			Username: user,
-			Email:    email,
-		},
+	ctxlog.FromContext(ctx).WithFields(logrus.Fields{
+		"user":  user,
+		"email": email,
+	}).Debug("pam authentication succeeded")
+	return createAPIClientAuthorization(ctx, ctrl.RailsProxy, ctrl.Cluster.SystemRootToken, rpc.UserSessionAuthInfo{
+		Username: user,
+		Email:    email,
 	})
-	if err != nil {
-		return arvados.APIClientAuthorization{}, err
-	}
-	target, err := url.Parse(resp.RedirectLocation)
-	if err != nil {
-		return arvados.APIClientAuthorization{}, err
-	}
-	token := target.Query().Get("api_token")
-	return ctrl.RailsProxy.APIClientAuthorizationCurrent(auth.NewContext(ctx, auth.NewCredentials(token)), arvados.GetOptions{})
 }
diff --git a/sdk/go/arvados/config.go b/sdk/go/arvados/config.go
index 38de6b8ea..4a831d765 100644
--- a/sdk/go/arvados/config.go
+++ b/sdk/go/arvados/config.go
@@ -135,6 +135,19 @@ type Cluster struct {
 		Repositories string
 	}
 	Login struct {
+		LDAP struct {
+			Enable                  bool
+			URL                     URL
+			StartTLS                bool
+			InsecureTLS             bool
+			StripDomain             string
+			AppendDomain            string
+			LookupUsernameAttribute string
+			LookupBase              string
+			LookupExtraFilters      string
+			EmailDomain             string
+			EmailAttributes         string
+		}
 		GoogleClientID                string
 		GoogleClientSecret            string
 		GoogleAlternateEmailAddresses bool

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list