3 # Copyright (C) The Arvados Authors. All rights reserved.
5 # SPDX-License-Identifier: AGPL-3.0
7 # This script demonstrates using LDAP for Arvados user authentication.
9 # It configures arvados controller in a docker container, optionally
10 # with pam_ldap(5) configured to authenticate against an OpenLDAP
11 # server in a second docker container.
13 # After adding a "foo" user entry, it uses curl to check that the
14 # Arvados controller's login endpoint accepts the "foo" account
15 # username/password and rejects invalid credentials.
17 # It is intended to be run inside .../build/run-tests.sh (in
18 # interactive mode: "test lib/controller/localdb -tags=docker
19 # -check.f=LDAP -check.vv"). It assumes ARVADOS_TEST_API_HOST points
20 # to a RailsAPI server and the desired version of arvados-server is
21 # installed in $GOPATH/bin.
26 if [[ -n ${ARVADOS_DEBUG} ]]; then
31 case "${config_method}" in
35 echo >&2 "\$config_method env var must be 'pam' or 'ldap'"
40 hostname="$(hostname)"
45 for h in ${ldapctr} ${ctrlctr}; do
46 if [[ -n ${h} ]]; then
53 if [[ -z "$(docker image ls -q osixia/openldap:1.3.0)" ]]; then
54 echo >&2 "Pulling docker image for ldap server"
55 docker pull osixia/openldap:1.3.0
58 ldapctr=ldap-${RANDOM}
59 echo >&2 "Starting ldap server in docker container ${ldapctr}"
60 docker run --rm --detach \
64 docker logs --follow ${ldapctr} 2>$debug >$debug &
65 ldaphostport=$(docker port ${ldapctr} 389/tcp)
66 ldapport=${ldaphostport##*:}
67 ldapurl="ldap://${hostname}:${ldapport}"
68 passwordhash="$(docker exec -i ${ldapctr} slappasswd -s "secret")"
70 # These are the default admin credentials for osixia/openldap:1.3.0
74 cat >"${tmpdir}/zzzzz.yml" <<EOF
84 password: insecure_arvados_test
85 ManagementToken: e687950a23c3a9bceec28c6223a06c79
86 SystemRootToken: systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy
92 BlobSigningKey: zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc
94 ForwardSlashNameSubstitution: /
98 "https://${hostname}:${ARVADOS_TEST_API_HOST##*:}/": {}
100 ExternalURL: http://0.0.0.0:9999/
102 "http://0.0.0.0:9999/": {}
106 case "${config_method}" in
108 setup_pam_ldap="apt update && DEBIAN_FRONTEND=noninteractive apt install -y ldap-utils libpam-ldap && pam-auth-update --package /usr/share/pam-configs/ldap"
109 cat >>"${tmpdir}/zzzzz.yml" <<EOF
113 # Without this specific DefaultEmailDomain, inserted users
114 # would prevent subsequent database/reset from working (see
115 # database_controller.rb).
116 DefaultEmailDomain: example.com
121 cat >>"${tmpdir}/zzzzz.yml" <<EOF
127 SearchBase: dc=example,dc=org
128 SearchBindUser: cn=admin,dc=example,dc=org
129 SearchBindPassword: admin
134 cat >&2 "${tmpdir}/zzzzz.yml"
136 cat >"${tmpdir}/pam_ldap.conf" <<EOF
137 base dc=example,dc=org
141 binddn cn=${adminuser},dc=example,dc=org
142 bindpw ${adminpassword}
145 cat >"${tmpdir}/add_example_user.ldif" <<EOF
146 dn: cn=bar,dc=example,dc=org
147 objectClass: posixGroup
151 description: "Example group 'bar'"
153 dn: uid=foo-bar,dc=example,dc=org
158 mail: foo-bar-baz@example.com
159 objectClass: inetOrgPerson
160 objectClass: posixAccount
162 objectClass: shadowAccount
166 shadowLastChange: 10701
167 loginShell: /bin/bash
170 homeDirectory: /home/foo-bar
171 userPassword: ${passwordhash}
174 echo >&2 "Adding example user entry user=foo-bar pass=secret (retrying until server comes up)"
175 docker run --rm --entrypoint= \
176 -v "${tmpdir}/add_example_user.ldif":/add_example_user.ldif:ro \
177 osixia/openldap:1.3.0 \
178 bash -c "for f in \$(seq 1 5); do if ldapadd -H '${ldapurl}' -D 'cn=${adminuser},dc=example,dc=org' -w '${adminpassword}' -f /add_example_user.ldif; then exit 0; else sleep 2; fi; done; echo 'failed to add user entry'; exit 1"
180 echo >&2 "Building arvados controller binary to run in container"
181 go build -o "${tmpdir}" ../../../cmd/arvados-server
183 ctrlctr=ctrl-${RANDOM}
184 echo >&2 "Starting arvados controller in docker container ${ctrlctr}"
185 docker run --detach --rm --name=${ctrlctr} \
187 -v "${tmpdir}/pam_ldap.conf":/etc/pam_ldap.conf:ro \
188 -v "${tmpdir}/arvados-server":/bin/arvados-server:ro \
189 -v "${tmpdir}/zzzzz.yml":/etc/arvados/config.yml:ro \
190 -v $(realpath "${PWD}/../../.."):/arvados:ro \
192 bash -c "${setup_pam_ldap:-true} && arvados-server controller"
193 docker logs --follow ${ctrlctr} 2>$debug >$debug &
194 ctrlhostport=$(docker port ${ctrlctr} 9999/tcp)
196 echo >&2 "Waiting for arvados controller to come up..."
197 for f in $(seq 1 20); do
198 if curl -s "http://${ctrlhostport}/arvados/v1/config" >/dev/null; then
206 echo >&2 "Arvados controller is up at http://${ctrlhostport}"
211 if ! echo "${resp}" | fgrep -q "${str}"; then
213 echo >&2 "FAIL: expected in response, but not found: ${str@Q}"
220 echo >&2 "Testing authentication failure"
221 resp="$(set -x; curl -s --include -d username=foo-bar -d password=nosecret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
222 check_contains "${resp}" "HTTP/1.1 401"
223 if [[ "${config_method}" = ldap ]]; then
224 check_contains "${resp}" '{"errors":["LDAP: Authentication failure (with username \"foo-bar\" and password)"]}'
226 check_contains "${resp}" '{"errors":["PAM: Authentication failure (with username \"foo-bar\" and password)"]}'
229 echo >&2 "Testing authentication success"
230 resp="$(set -x; curl -s --include -d username=foo-bar -d password=secret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
231 check_contains "${resp}" "HTTP/1.1 200"
232 check_contains "${resp}" '"api_token":"'
233 check_contains "${resp}" '"scopes":["all"]'
234 check_contains "${resp}" '"uuid":"zzzzz-gj3su-'
236 secret="${resp##*api_token\":\"}"
237 secret="${secret%%\"*}"
238 uuid="${resp##*uuid\":\"}"
240 token="v2/$uuid/$secret"
241 echo >&2 "New token is ${token}"
243 resp="$(set -x; curl -s --include -H "Authorization: Bearer ${token}" "http://${ctrlhostport}/arvados/v1/users/current" | tee $debug)"
244 check_contains "${resp}" "HTTP/1.1 200"
245 if [[ "${config_method}" = ldap ]]; then
246 # user fields come from LDAP attributes
247 check_contains "${resp}" '"first_name":"Foo"'
248 check_contains "${resp}" '"last_name":"Bar"'
249 check_contains "${resp}" '"username":"foobar"' # "-" removed by rails api
250 check_contains "${resp}" '"email":"foo-bar-baz@example.com"'
252 # PAMDefaultEmailDomain
253 check_contains "${resp}" '"email":"foo-bar@example.com"'