[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