Feature #16212

Can choose PAM as an authentication backend

Added by Peter Amstutz 29 days ago. Updated about 13 hours ago.

Status:
In Progress
Priority:
Normal
Assigned To:
Category:
API
Target version:
Start date:
03/17/2020
Due date:
% Done:

0%

Estimated time:
(Total: 0.00 h)
Story points:
-

Description

Works for Unix local user database PAM backend.

Try it with LDAP backend. And Kerberos?

Controller accepts username / password and uses PAM to authenticate user.

http://www.linux-pam.org/ has guides


Subtasks

Task #16234: Review 16212-pam-loginIn ProgressPeter Amstutz

Task #16259: Support username/password login in Workbench2In ProgressLucas Di Pentima

Task #16307: Review 16212-login-form (WB2 repo)In ProgressPeter Amstutz


Related issues

Related to Arvados Epics - Story #15322: Replace and delete sso-providerNew03/11/202004/22/2020

Blocks Arvados - Feature #15881: [controller] LDAP login supportIn Progress

History

#1 Updated by Peter Amstutz 29 days ago

  • Description updated (diff)

#2 Updated by Peter Amstutz 29 days ago

#3 Updated by Peter Amstutz 29 days ago

  • Related to Story #15322: Replace and delete sso-provider added

#4 Updated by Peter Amstutz 28 days ago

  • Description updated (diff)

#5 Updated by Peter Amstutz 28 days ago

Interesting PAM modules (debian packages)

libpam-krb5 - PAM module for MIT Kerberos
libpam-ldapd - PAM module for using LDAP as an authentication service
libpam-mklocaluser - Configure PAM to create a local user if it do not exist already (for dev / demo)

#6 Updated by Tom Clegg 22 days ago

  • Assigned To set to Tom Clegg

#7 Updated by Tom Clegg 21 days ago

  • Status changed from New to In Progress

#8 Updated by Tom Clegg 20 days ago

Server side:

16212-pam-login @ d739042d5aedd9a2cef19deb591cccc57d639353 -- https://ci.arvados.org/view/Developer/job/developer-run-tests/1773/ (tests fail because jenkins worker image doesn't have libpam-dev)

Workbench side isn't implemented yet, but it should work like this when Login.PAM is true in the server's exported config:
  • Prompt user for username and password
  • POST https://{apihost}/login, with username=x, password=y, and _method=GET in the request body (or "X-Http-Method-Override: GET" header instead of _method=GET)
  • Get API token from "token" field from the response body
  • If the "token" value is empty/missing, show the string in the "message" field, and allow the user to retry

#9 Updated by Tom Clegg 9 days ago

16212-pam-login @ 4e0eb166fd808b32c10cccc2b4014a02edcf29a6 adds a test that authenticates to an OpenLDAP server through PAM.

It's disabled by default because it's a bit heavy and requires docker. You can run it from run-tests.sh interactive mode with "test lib/controller/localdb -tags docker -check.f=LDAP".

#10 Updated by Peter Amstutz 9 days ago

The LDAP test is being uncooperative:

Adding example user entry user=foo pass=secret (retrying until server comes up)
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
failed to add user entry
ldap-25941

----------------------------------------------------------------------
FAIL: login_pam_docker_test.go:17: PamSuite.TestLoginLDAPViaPAM

login_pam_docker_test.go:22:
    c.Check(err, check.IsNil)
... value *exec.ExitError = &exec.ExitError{ProcessState:(*os.ProcessState)(0xc000316000), Stderr:[]uint8(nil)} ("exit status 1")

OOPS: 0 passed, 1 FAILED
--- FAIL: Test (179.04s)
FAIL

#11 Updated by Peter Amstutz 8 days ago

I'm trying to authenticate manually:

$ curl  -XPOST -F username=foo -F password=bar -k https://172.17.0.2:8000/login?_method=GET
{"message":"Authentication failure"}

2020-03-24_23:27:59.01338 {"PID":6052,"RequestID":"req-19dm6gasl4kkmgtt4bcy","level":"info","msg":"request","remoteAddr":"127.0.0.1:36490",
"reqBytes":246,"reqForwardedFor":"172.17.0.1","reqHost":"172.17.0.2:8000","reqMethod":"POST",
"reqPath":"login","reqQuery":"_method=GET","time":"2020-03-24T23:27:59.013280588Z"}

2020-03-24_23:28:01.34490 {"PID":6052,"RequestID":"req-19dm6gasl4kkmgtt4bcy","level":"info","msg":"response","remoteAddr":"127.0.0.1:36490",
"reqBytes":246,"reqForwardedFor":"172.17.0.1","reqHost":"172.17.0.2:8000","reqMethod":"POST",
"reqPath":"login","reqQuery":"_method=GET","respBytes":37,"respStatus":"OK","respStatusCode":200,
"time":"2020-03-24T23:28:01.344837921Z","timeToStatus":2.331543,"timeTotal":2.331551,"timeWriteBody":0.000007}

  • Nothing logged indicating why it is rejected
  • Return code is 200, should be an error like 401 or 403

#12 Updated by Peter Amstutz 8 days ago

  • Target version changed from 2020-03-25 Sprint to 2020-04-08 Sprint

#13 Updated by Peter Amstutz 8 days ago

  • Target version changed from 2020-04-08 Sprint to 2020-03-25 Sprint

Also _method=GET doesn't seem to work if it appears in the body of the request, only the query, so #note-8 probably won't work

$ curl  -XPOST -F username=foo -F password=bar -F _method=GET -k https://172.17.0.2:8000/login
{"errors":["API endpoint not found"]}

Using the override in the header seems to work (I still get Authentication failure though, with no information how to debug):

$ curl  -XPOST -F username=foo -F password=bar  -H "X-Http-Method-Override: GET" -k https://172.17.0.2:8000/login
{"message":"Authentication failure"}

#14 Updated by Peter Amstutz 8 days ago

This branch also needs an install documentation update about how to use PAM.

#15 Updated by Peter Amstutz 8 days ago

  • Target version changed from 2020-03-25 Sprint to 2020-04-08 Sprint

#16 Updated by Peter Amstutz 6 days ago

I think this should support HTTP basic auth, because it would make it possible for Workbench 1 to support the new login strategy with little or no change.

  1. The workbench 1 login button sends you to the controller /login endpoint
  2. It responds with 401 with supported method HTTP basic auth
  3. The browser collects the username and password and resubmits using basic auth
  4. Controller responds with a redirect back to workbench with the API token in the URL.

#17 Updated by Lucas Di Pentima 5 days ago

I've been trying to send the POST request from WB2 and getting CORS problems. The following made the browser accept the outgoing request, is it correct?

diff --git a/lib/controller/router/router.go b/lib/controller/router/router.go
index 69d707703..b2fd5e4ff 100644
--- a/lib/controller/router/router.go
+++ b/lib/controller/router/router.go
@@ -382,11 +382,11 @@ func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() int

 func (rtr *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        switch strings.SplitN(strings.TrimLeft(r.URL.Path, "/"), "/", 2)[0] {
-       case "login", "logout", "auth":
+       case "logout", "auth":
        default:
                w.Header().Set("Access-Control-Allow-Origin", "*")
                w.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, PUT, POST, PATCH, DELETE")
-               w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
+               w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Http-Method-Override")
                w.Header().Set("Access-Control-Max-Age", "86486400")
        }
        if r.Method == "OPTIONS" {

#18 Updated by Lucas Di Pentima 3 days ago

Workbench 2 PAM Login feature is (almost?) ready at commit: a308a27833843d90405b927ac491fea7c853b91c - branch 16212-login-form
I'm getting Authentication failure errors even if I started a fresh arvbox instance with the following config override file:

Clusters:
  x3sgo:
    Login:
      PAM: true
      PAMService: arvados
      ProviderAppID: "" 

#19 Updated by Lucas Di Pentima 2 days ago

Working on a missing case on WB2: The ability to display the login form when selecting remote clusters with Login.PAM: true

#20 Updated by Tom Clegg 1 day ago

I've updated this branch with a new endpoint (POST /arvados/v1/users/authenticate) for password authentication. This solves a few problems:
  • This endpoint can safely accept CORS requests (cross-origin GET /login reqs can't be allowed because they might be forwarded to RailsAPI, which returns a token based on the request cookies/session -- and changing CORS based on auth config would be too fragile).
  • No _method=GET hack needed
  • No dual-personality LoginOptions struct
Also fixed:
  • Docker test: build an arvados-controller binary to use in the container, in case there are changes since the last time "install cmd/arvados-server" ran
  • Return 401 (or other suitable code) instead of 200 when authentication fails
  • More detail in failure messages. We don't get much information to convey, but at least we can mention the word PAM, and if the password is never even requested, we can mention that in case it's a useful clue.
  • If needed, pull the ldap server docker image explicitly before calling docker-run. Perhaps this will help avoid the timeout encountered in note-10.

The case for HTTP authentication doesn't sound compelling to me. It invariably results in terrible UX. If we need something better than linking WB1 to WB2's login, I think it would make more sense to add a form on WB1.

Admin docs do need to be added but I don't think it's a blocker for merging the backend.

16212-pam-login @ 16b5f7275ffa2bd4347134f7269744f4cd4baa2a -- https://ci.arvados.org/view/Developer/job/developer-run-tests/1793/

#21 Updated by Lucas Di Pentima 1 day ago

Thanks for the fixes and more verbose messaging.

Using arvbox I'm having an issue on WB2 that I've just was able to reproduce using curl.

The following (as per you docker test) fails:

$ curl -s --include -d username=foo -d password=bar -k https://controller:8000/arvados/v1/users/authenticate
HTTP/1.1 401 Unauthorized
Server: nginx/1.10.3
Date: Tue, 31 Mar 2020 21:58:09 GMT
Content-Type: application/json
Content-Length: 80
Connection: keep-alive
Access-Control-Allow-Headers: Authorization, Content-Type, X-Http-Method-Override
Access-Control-Allow-Methods: GET, HEAD, PUT, POST, PATCH, DELETE
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 86486400
X-Content-Type-Options: nosniff

{"errors":["PAM: Authentication failure (with username \"foo\" and password)"]}

Then, if using curl with -F, it fails the same way than on WB2:

curl -s --include -X POST -F username=foo -F password=bar -k https://controller:8000/arvados/v1/users/authenticate
HTTP/1.1 401 Unauthorized
Server: nginx/1.10.3
Date: Tue, 31 Mar 2020 22:01:08 GMT
Content-Type: application/json
Content-Length: 77
Connection: keep-alive
Access-Control-Allow-Headers: Authorization, Content-Type, X-Http-Method-Override
Access-Control-Allow-Methods: GET, HEAD, PUT, POST, PATCH, DELETE
Access-Control-Allow-Origin: *
Access-Control-Max-Age: 86486400
X-Content-Type-Options: nosniff

{"errors":["PAM: Authentication failure (with username \"\" and password)"]}

Just for the record, I've run arvbox shell and created the foo user with password bar, it should be enough for the test to work, right?

#22 Updated by Lucas Di Pentima about 17 hours ago

Commit c217a294 at WB2 branch updates the login form to use the new endpoint and data url-encoding.

Still not able to fully test it as the PAM feature isn't working for me on arvbox, what I may be missing?

#23 Updated by Lucas Di Pentima about 17 hours ago

Figured out why it isn’t working on arvbox: because the process should be running as root, or else the auth process only works for the running process’ user… so I got into arvbox shell, set arvbox user a password, et voilá! :)

Taken from: https://pkg.go.dev/github.com/msteinert/pam?tab=doc#example-package-Authenticate

So on arvbox (and anywhere else?), arvados-controller should be running as root.

#24 Updated by Tom Clegg about 13 hours ago

16212-pam-login @ 7010ed0b94f9c572f2f7220a2a1eb17b61325fe7 -- https://ci.arvados.org/view/Developer/job/developer-run-tests/1799/
  • return a "u/p auth not available" error (instead of forwarding the request to Rails and getting a "not logged in" error) if the arvados/v1/users/authenticate endpoint is used when PAM is not enabled
  • handle the arvados/v1/users/authenticate endpoint in controller, even when ForceLegacyAPI14 mode is enabled (there is no legacy API for this)

Also available in: Atom PDF