#!/usr/bin/perl # (C) Nginx, Inc. # Test for nginx-ldap-auth daemon with OpenLDAP. ############################################################################### use warnings; use strict; use Test::More; use MIME::Base64; BEGIN { use FindBin; chdir($FindBin::Bin); } use lib 'lib'; use Test::Nginx; ############################################################################### select STDERR; $| = 1; select STDOUT; $| = 1; my $t = Test::Nginx->new()->has(qw/http proxy rewrite auth_request/) ->write_file_expand('nginx.conf', <<'EOF'); %%TEST_GLOBALS%% events { } daemon off; http { %%TEST_GLOBALS_HTTP%% #proxy_cache_path cache/ keys_zone=auth_cache:10m; server { listen 127.0.0.1:8082; location / { return 200 "ACCESS GRANTED\n"; } location /login { return 200 "LOGIN PAGE\n"; } } upstream backend { server 127.0.0.1:8082; } server { listen 127.0.0.1:8080; location / { auth_request /auth-proxy; error_page 401 =200 /login; proxy_pass http://backend/; } location /ssl { auth_request /auth-proxy-ssl; error_page 401 =200 /login; proxy_pass http://backend/; } location /starttls { auth_request /auth-proxy-starttls; error_page 401 =200 /login; proxy_pass http://backend/; } location /nodn { auth_request /auth-nodn; error_page 401 =200 /login; proxy_pass http://backend/; } location /nourl { auth_request /auth-nourl; error_page 401 =200 /login; proxy_pass http://backend/; } location /ref1 { auth_request /auth-ref1; error_page 401 =200 /login; proxy_pass http://backend/; } location /query-injection { auth_request /auth-query-injection; error_page 401 =200 /login; proxy_pass http://backend/; } location /login { proxy_pass http://backend/login; proxy_set_header X-Target $request_uri; } location = /auth-proxy { internal; proxy_pass http://127.0.0.1:8888; proxy_pass_request_body off; proxy_set_header Content-Length ""; #proxy_cache auth_cache; #proxy_cache_valid 200 10m; #proxy_cache_key "$http_authorization$cookie_nginxauth"; proxy_set_header X-Ldap-URL "ldap://127.0.0.1:8083"; proxy_set_header X-Ldap-BaseDN "ou=Users,dc=test,dc=local"; proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local"; proxy_set_header X-Ldap-BindPass "secret"; proxy_set_header X-CookieName "nginxauth"; proxy_set_header Cookie nginxauth=$cookie_nginxauth; #proxy_set_header X-Ldap-Starttls "true"; #proxy_set_header X-Ldap-Template "(sAMAccountName=%(username)s)"; #proxy_set_header X-Ldap-DisableReferrals "true"; #proxy_set_header X-Ldap-Template "(cn=%(username)s)"; #proxy_set_header X-Ldap-Realm "Restricted"; } location = /auth-proxy-ssl { internal; proxy_pass http://127.0.0.1:8888; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Ldap-URL "ldaps://127.0.0.1:8084"; proxy_set_header X-Ldap-BaseDN "ou=Users,dc=test,dc=local"; proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local"; proxy_set_header X-Ldap-BindPass "secret"; proxy_set_header X-CookieName "nginxauth"; proxy_set_header Cookie nginxauth=$cookie_nginxauth; #proxy_set_header X-Ldap-Starttls "true"; } location = /auth-proxy-starttls { internal; proxy_pass http://127.0.0.1:8888; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Ldap-URL "ldap://127.0.0.1:8083"; proxy_set_header X-Ldap-BaseDN "ou=Users,dc=test,dc=local"; proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local"; proxy_set_header X-Ldap-BindPass "secret"; proxy_set_header X-CookieName "nginxauth"; proxy_set_header Cookie nginxauth=$cookie_nginxauth; proxy_set_header X-Ldap-Starttls "true"; } location = /auth-nodn { internal; proxy_pass http://127.0.0.1:8888; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Ldap-URL "ldap://127.0.0.1:8083"; proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local"; proxy_set_header X-Ldap-BindPass "secret"; } location = /auth-nourl { internal; proxy_pass http://127.0.0.1:8888; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Ldap-BaseDN "ou=Users,dc=test,dc=local"; proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local"; proxy_set_header X-Ldap-BindPass "secret"; } location = /auth-ref1 { internal; proxy_pass http://127.0.0.1:8888; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Ldap-URL "ldap://127.0.0.1:8083"; proxy_set_header X-Ldap-BaseDN "ou=Users,dc=test,dc=local"; proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local"; proxy_set_header X-Ldap-BindPass "secret"; proxy_set_header X-CookieName "nginxauth"; proxy_set_header Cookie nginxauth=$cookie_nginxauth; } location = /auth-query-injection { internal; proxy_pass http://127.0.0.1:8888; proxy_pass_request_body off; proxy_set_header Content-Length ""; proxy_set_header X-Ldap-URL "ldap://127.0.0.1:8083"; proxy_set_header X-Ldap-BaseDN "ou=Users,dc=test,dc=local"; proxy_set_header X-Ldap-BindDN "cn=root,dc=test,dc=local"; proxy_set_header X-Ldap-BindPass "secret"; proxy_set_header X-CookieName "nginxauth"; proxy_set_header Cookie nginxauth=$cookie_nginxauth; proxy_set_header X-Ldap-Template '(|(&(memberOf=superadmin)(cn=%(username)s))(&(memberOf=admin)(cn=%(username)s)))'; } } } EOF my $d = $t->testdir(); $t->write_file('openssl.conf', <>$d/openssl.out 2>&1") == 0 or die "Can't create certificate for $name: $!\n"; } $t->write_file_expand("slapd.conf", <<"EOF"); include /etc/openldap/schema/core.schema include /etc/openldap/schema/cosine.schema include /etc/openldap/schema/inetorgperson.schema include /etc/openldap/schema/nis.schema include /etc/openldap/schema/misc.schema pidfile $d/slapd.pid argsfile $d/slapd.args logfile $d/slapd.log loglevel 256 64 access to dn.base="" by * read access to dn.base="cn=Subschema" by * read access to * by self write by users read by anonymous read moduleload back_hdb database hdb suffix "dc=test,dc=local" rootdn "cn=root,dc=test,dc=local" rootpw secret directory $d/openldap-data index objectClass eq TLSCipherSuite HIGH:MEDIUM:+SSLv2 TLSCACertificateFile $d/localhost.crt TLSCertificateFile $d/localhost.crt TLSCertificateKeyFile $d/localhost.key EOF $t->write_file_expand("slapd2.conf", <<"EOF"); include /etc/openldap/schema/core.schema include /etc/openldap/schema/cosine.schema include /etc/openldap/schema/inetorgperson.schema include /etc/openldap/schema/nis.schema include /etc/openldap/schema/misc.schema pidfile $d/slapd2.pid argsfile $d/slapd2.args logfile $d/slapd2.log loglevel 256 64 access to dn.base="" by * read access to dn.base="cn=Subschema" by * read access to * by self write by users read by anonymous read moduleload back_hdb database hdb suffix "ou=Users, dc=test,dc=local" rootdn "cn=root, ou=Users, dc=test,dc=local" rootpw secret directory $d/openldap2-data index objectClass eq TLSCipherSuite HIGH:MEDIUM:+SSLv2 TLSCACertificateFile $d/localhost.crt TLSCertificateFile $d/localhost.crt TLSCertificateKeyFile $d/localhost.key # our upstream referral ldap://127.0.0.1:%%PORT_8083%%/ EOF $t->write_file_expand("initial.ldif", <<'EOF'); dn: dc=test,dc=local dc: test description: Test-OU objectClass: dcObject objectClass: organization o: Example, Inc. dn: ou=Users, dc=test,dc=local ou: Users description: All people in organisation objectclass: organizationalunit dn: cn=user1,ou=Users,dc=test,dc=local objectclass: inetOrgPerson cn: User1 sn: u1 uid: user1 userpassword: user1secret mail: user1@example.com description: user1 ou: Users dn: cn=user2,ou=Users,dc=test,dc=local objectclass: inetOrgPerson cn: User2 sn: u2 uid: user2 userpassword: user2secret mail: user2@example.com description: user2 ou: Users dn: cn=user3,ou=Users,dc=test,dc=local objectclass: inetOrgPerson cn: User3 sn: u3 uid: user3 userpassword: user3secret mail: user3@example.com description: user3 ou: Users dn: ou=more,ou=Users,dc=test,dc=local objectClass: referral objectClass: extensibleObject dc: subtree ref: ldap://127.0.0.1:%%PORT_8085%%/ou=more,ou=Users,dc=test,dc=local EOF $t->write_file_expand("initial2.ldif", <<'EOF'); dn: ou=Users, dc=test,dc=local ou: Users description: All people in organisation objectclass: organizationalunit dn: ou=more,ou=Users,dc=test,dc=local dc: test description: Test-OU objectClass: dcObject objectClass: organizationalUnit dn: cn=user4, ou=more, ou=Users,dc=test,dc=local objectclass: inetOrgPerson cn: User4 sn: u4 uid: user4 userpassword: user4secret mail: user4@example.com description: user4 ou: Users EOF # -u ldap -g ldap my $SLAPD = defined $ENV{TEST_LDAP_DAEMON} ? $ENV{TEST_LDAP_DAEMON} : '/usr/lib64/openldap/slapd'; my $AUTHD = defined $ENV{TEST_LDAP_AUTH_DAEMON} ? $ENV{TEST_LDAP_AUTH_DAEMON} : 'nginx-ldap-auth-daemon.py'; $t->has_daemon($SLAPD); $t->has_daemon($AUTHD); mkdir("$d/openldap-data"); mkdir("$d/openldap2-data"); my $p3 = port(8083); my $p4 = port(8084); my $p5 = port(8085); # change '0' to '1' or more to get debug from slapd $t->run_daemon($SLAPD, '-d', '0', '-f', "$d/slapd.conf", '-h', "ldap://127.0.0.1:$p3 ldaps://127.0.0.1:$p4"); $t->run_daemon($SLAPD, '-d', '0', '-f', "$d/slapd2.conf", '-h', "ldap://127.0.0.1:$p5"); $t->waitforsocket("127.0.0.1:$p3") or die "Can't start slapd"; $t->waitforsocket("127.0.0.1:$p5") or die "Can't start slapd2"; system("ldapadd -H ldap://127.0.0.1:$p3 -x -D \"cn=root,dc=test,dc=local\"" . " -f $d/initial.ldif -w secret >> $d/ldif.log 2>&1") == 0 or die "Can't import initial LDIF\n"; system("ldapadd -H ldap://127.0.0.1:$p5 -x -D \"cn=root,ou=Users,dc=test,dc=local\"" . " -f $d/initial2.ldif -w secret >> $d/ldif2.log 2>&1") == 0 or die "Can't import initial2 LDIF\n"; $t->write_file_expand("auth_daemon.sh", <<"EOF"); AUTHBIN=\$(realpath $AUTHD) cd $d exec coverage run \$AUTHBIN --host 127.0.0.1 \\ -p %%PORT_8888%% >$d/nginx-ldap-auth-dameon.stdlog 2>&1 EOF $t->run_daemon('/bin/sh', "$d/auth_daemon.sh"); $t->waitforsocket('127.0.0.1:' . port(8888)) or die "Can't start auth daemon"; $t->plan(22); $t->run(); ############################################################################### like(http_get_auth('/', 'user1', 'user1secret'), qr!ACCESS GRANTED!, 'proper user with proper pass'); like(http_get_auth('/', 'user1', 'randompass'), qr!LOGIN PAGE!, 'proper user with incorrect pass'); like(http_get_auth('/', 'user111', 'user1secret'), qr!LOGIN PAGE!, 'similar user with user1 pass'); like(http_get_auth('/', 'randomuser', 'randompass'), qr!LOGIN PAGE!, 'random user with random pass'); like(http_get_auth('/', 'user2', 'user2secret'), qr!ACCESS GRANTED!, 'user2 with proper pass'); like(http_get_auth('/', 'user3', 'user3secret'), qr!ACCESS GRANTED!, 'user3 with proper pass'); like(http_get_auth('/', '', ''), qr!LOGIN PAGE!, 'empty user no password'); like(http_get('/'), qr!LOGIN PAGE!, 'no auth header'); like(http_get_cookie('/', 'user1', 'user1secret'), qr!ACCESS GRANTED!, 'proper user with proper pass cookie'); like(http_get_cookie('/', 'user1', 'randompasz'), qr!LOGIN PAGE!, 'proper user with incorrect pass cookie'); like(http_get_cookie('/', 'randomuser', 'randompass'), qr!LOGIN PAGE!, 'random user with random pass cookie'); like(http_get_cookie('/', 'user2', 'user2secret'), qr!ACCESS GRANTED!, 'user2 with proper pass cookie'); like(http_get_cookie('/', 'user3', 'user3secret'), qr!ACCESS GRANTED!, 'user3 with proper pass cookie'); like(http_get_auth_broken_base64('/', 'user3', 'user3secret'), qr!LOGIN PAGE!, 'user3 with proper pass broken base64'); like(http_get_cookie_broken_base64('/', 'user3', 'user3secret'), qr!LOGIN PAGE!, 'user3 with proper pass broken cookie'); like(http_get_auth('/ssl', 'user1', 'user1secret'), qr!ACCESS GRANTED!, 'proper user with proper pass with ssl'); like(http_get_auth('/starttls', 'user1', 'user1secret'), qr!ACCESS GRANTED!, 'proper user with proper pass with starttls'); # dn is not set, no default, daemon error => 502 like(http_get_auth('/nodn', 'user1', 'user1secret'), qr!Internal Server Error!, 'dn must be set'); # url is not set, default is used, which is not accessible => login page like(http_get_auth('/nourl', 'user1', 'user1secret'), qr!LOGIN PAGE!, 'url must be set'); # LDAP referrals # user can be found, but bind happens on 1st server, instead of the found # the behaviour may change with different servers like(http_get_auth('/ref1', 'user4', 'user4secret'), qr!LOGIN PAGE!, 'server2 user via referral on server1'); # unknown user on referred server, result is empty dn like(http_get_auth('/ref1', 'unknow_user', 'unknowpassword'), qr!LOGIN PAGE!, 'unknown user with referral on server1'); # LDAP Query Injection result in 401 like(http_get_auth('/query-injection', 'user1))(|(cn=user1', 'user1secret'), qr!LOGIN PAGE!, 'Injection Attempt in Username will be escaped and blocked.'); ############################################################################### sub http_get_auth { my ($url, $user, $password) = @_; my $auth = encode_base64($user . ':' . $password, ''); return http(<