15881: Move Google, SSO, and PAM configs into their own sections.
[arvados.git] / lib / controller / localdb / login_ldap_docker_test.sh
1 #!/bin/bash
2
3 # This script demonstrates using LDAP for Arvados user authentication.
4 #
5 # It configures arvados controller in a docker container, optionally
6 # with pam_ldap(5) configured to authenticate against an OpenLDAP
7 # server in a second docker container.
8 #
9 # After adding a "foo" user entry, it uses curl to check that the
10 # Arvados controller's login endpoint accepts the "foo" account
11 # username/password and rejects invalid credentials.
12 #
13 # It is intended to be run inside .../build/run-tests.sh (in
14 # interactive mode: "test lib/controller/localdb -tags=docker
15 # -check.f=LDAP -check.vv"). It assumes ARVADOS_TEST_API_HOST points
16 # to a RailsAPI server and the desired version of arvados-server is
17 # installed in $GOPATH/bin.
18
19 set -e -o pipefail
20
21 debug=/dev/null
22 if [[ -n ${ARVADOS_DEBUG} ]]; then
23     debug=/dev/stderr
24     set -x
25 fi
26
27 case "${config_method}" in
28     pam | ldap)
29         ;;
30     *)
31         echo >&2 "\$config_method env var must be 'pam' or 'ldap'"
32         exit 1
33         ;;
34 esac
35
36 hostname="$(hostname)"
37 tmpdir="$(mktemp -d)"
38 cleanup() {
39     trap - ERR
40     rm -r ${tmpdir}
41     for h in ${ldapctr} ${ctrlctr}; do
42         if [[ -n ${h} ]]; then
43             docker kill ${h}
44         fi
45     done
46 }
47 trap cleanup ERR
48
49 if [[ -z "$(docker image ls -q osixia/openldap:1.3.0)" ]]; then
50     echo >&2 "Pulling docker image for ldap server"
51     docker pull osixia/openldap:1.3.0
52 fi
53
54 ldapctr=ldap-${RANDOM}
55 echo >&2 "Starting ldap server in docker container ${ldapctr}"
56 docker run --rm --detach \
57        -p 389 -p 636 \
58        --name=${ldapctr} \
59        osixia/openldap:1.3.0
60 docker logs --follow ${ldapctr} 2>$debug >$debug &
61 ldaphostport=$(docker port ${ldapctr} 389/tcp)
62 ldapport=${ldaphostport##*:}
63 ldapurl="ldap://${hostname}:${ldapport}"
64 passwordhash="$(docker exec -i ${ldapctr} slappasswd -s "secret")"
65
66 # These are the default admin credentials for osixia/openldap:1.3.0
67 adminuser=admin
68 adminpassword=admin
69
70 cat >"${tmpdir}/zzzzz.yml" <<EOF
71 Clusters:
72   zzzzz:
73     PostgreSQL:
74       Connection:
75         client_encoding: utf8
76         host: ${hostname}
77         dbname: arvados_test
78         user: arvados
79         password: insecure_arvados_test
80     ManagementToken: e687950a23c3a9bceec28c6223a06c79
81     SystemRootToken: systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy
82     API:
83       RequestTimeout: 30s
84     TLS:
85       Insecure: true
86     Collections:
87       BlobSigningKey: zfhgfenhffzltr9dixws36j1yhksjoll2grmku38mi7yxd66h5j4q9w4jzanezacp8s6q0ro3hxakfye02152hncy6zml2ed0uc
88       TrustAllContent: true
89       ForwardSlashNameSubstitution: /
90     Services:
91       RailsAPI:
92         InternalURLs:
93           "https://${hostname}:${ARVADOS_TEST_API_HOST##*:}/": {}
94       Controller:
95         ExternalURL: http://0.0.0.0:9999/
96         InternalURLs:
97           "http://0.0.0.0:9999/": {}
98     SystemLogs:
99       LogLevel: debug
100 EOF
101 case "${config_method}" in
102     pam)
103         setup_pam_ldap="apt update && DEBIAN_FRONTEND=noninteractive apt install -y ldap-utils libpam-ldap && pam-auth-update --package /usr/share/pam-configs/ldap"
104         cat >>"${tmpdir}/zzzzz.yml" <<EOF
105     Login:
106       PAM:
107         Enable: true
108         # Without this specific DefaultEmailDomain, inserted users
109         # would prevent subsequent database/reset from working (see
110         # database_controller.rb).
111         DefaultEmailDomain: example.com
112 EOF
113         ;;
114     ldap)
115         setup_pam_ldap=""
116         cat >>"${tmpdir}/zzzzz.yml" <<EOF
117     Login:
118       LDAP:
119         Enable: true
120         URL: ${ldapurl}
121         StartTLS: false
122         SearchBase: dc=example,dc=org
123         SearchBindUser: cn=admin,dc=example,dc=org
124         SearchBindPassword: admin
125 EOF
126             ;;
127 esac
128
129 cat >&2 "${tmpdir}/zzzzz.yml"
130
131 cat >"${tmpdir}/pam_ldap.conf" <<EOF
132 base dc=example,dc=org
133 ldap_version 3
134 uri ${ldapurl}
135 pam_password crypt
136 binddn cn=${adminuser},dc=example,dc=org
137 bindpw ${adminpassword}
138 EOF
139
140 cat >"${tmpdir}/add_example_user.ldif" <<EOF
141 dn: cn=bar,dc=example,dc=org
142 objectClass: posixGroup
143 objectClass: top
144 cn: bar
145 gidNumber: 11111
146 description: "Example group 'bar'"
147
148 dn: uid=foo-bar,dc=example,dc=org
149 uid: foo-bar
150 cn: "Foo Bar"
151 givenName: Foo
152 sn: Bar
153 mail: foo-bar-baz@example.com
154 objectClass: inetOrgPerson
155 objectClass: posixAccount
156 objectClass: top
157 objectClass: shadowAccount
158 shadowMax: 180
159 shadowMin: 1
160 shadowWarning: 7
161 shadowLastChange: 10701
162 loginShell: /bin/bash
163 uidNumber: 11111
164 gidNumber: 11111
165 homeDirectory: /home/foo-bar
166 userPassword: ${passwordhash}
167 EOF
168
169 echo >&2 "Adding example user entry user=foo-bar pass=secret (retrying until server comes up)"
170 docker run --rm --entrypoint= \
171        -v "${tmpdir}/add_example_user.ldif":/add_example_user.ldif:ro \
172        osixia/openldap:1.3.0 \
173        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"
174
175 echo >&2 "Building arvados controller binary to run in container"
176 go build -o "${tmpdir}" ../../../cmd/arvados-server
177
178 ctrlctr=ctrl-${RANDOM}
179 echo >&2 "Starting arvados controller in docker container ${ctrlctr}"
180 docker run --detach --rm --name=${ctrlctr} \
181        -p 9999 \
182        -v "${tmpdir}/pam_ldap.conf":/etc/pam_ldap.conf:ro \
183        -v "${tmpdir}/arvados-server":/bin/arvados-server:ro \
184        -v "${tmpdir}/zzzzz.yml":/etc/arvados/config.yml:ro \
185        -v $(realpath "${PWD}/../../.."):/arvados:ro \
186        debian:10 \
187        bash -c "${setup_pam_ldap:-true} && arvados-server controller"
188 docker logs --follow ${ctrlctr} 2>$debug >$debug &
189 ctrlhostport=$(docker port ${ctrlctr} 9999/tcp)
190
191 echo >&2 "Waiting for arvados controller to come up..."
192 for f in $(seq 1 20); do
193     if curl -s "http://${ctrlhostport}/arvados/v1/config" >/dev/null; then
194         break
195     else
196         sleep 1
197     fi
198     echo -n >&2 .
199 done
200 echo >&2
201 echo >&2 "Arvados controller is up at http://${ctrlhostport}"
202
203 check_contains() {
204     resp="${1}"
205     str="${2}"
206     if ! echo "${resp}" | fgrep -q "${str}"; then
207         echo >&2 "${resp}"
208         echo >&2 "FAIL: expected in response, but not found: ${str@Q}"
209         return 1
210     fi
211 }
212
213 set +x
214
215 echo >&2 "Testing authentication failure"
216 resp="$(set -x; curl -s --include -d username=foo-bar -d password=nosecret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
217 check_contains "${resp}" "HTTP/1.1 401"
218 if [[ "${config_method}" = ldap ]]; then
219     check_contains "${resp}" '{"errors":["LDAP: Authentication failure (with username \"foo-bar\" and password)"]}'
220 else
221     check_contains "${resp}" '{"errors":["PAM: Authentication failure (with username \"foo-bar\" and password)"]}'
222 fi
223
224 echo >&2 "Testing authentication success"
225 resp="$(set -x; curl -s --include -d username=foo-bar -d password=secret "http://${ctrlhostport}/arvados/v1/users/authenticate" | tee $debug)"
226 check_contains "${resp}" "HTTP/1.1 200"
227 check_contains "${resp}" '"api_token":"'
228 check_contains "${resp}" '"scopes":["all"]'
229 check_contains "${resp}" '"uuid":"zzzzz-gj3su-'
230
231 secret="${resp##*api_token\":\"}"
232 secret="${secret%%\"*}"
233 uuid="${resp##*uuid\":\"}"
234 uuid="${uuid%%\"*}"
235 token="v2/$uuid/$secret"
236 echo >&2 "New token is ${token}"
237
238 resp="$(set -x; curl -s --include -H "Authorization: Bearer ${token}" "http://${ctrlhostport}/arvados/v1/users/current" | tee $debug)"
239 check_contains "${resp}" "HTTP/1.1 200"
240 if [[ "${config_method}" = ldap ]]; then
241     # user fields come from LDAP attributes
242     check_contains "${resp}" '"first_name":"Foo"'
243     check_contains "${resp}" '"last_name":"Bar"'
244     check_contains "${resp}" '"username":"foobar"' # "-" removed by rails api
245     check_contains "${resp}" '"email":"foo-bar-baz@example.com"'
246 else
247     # PAMDefaultEmailDomain
248     check_contains "${resp}" '"email":"foo-bar@example.com"'
249 fi
250
251 cleanup