From 4c3860e66b4a4f8108de793ddcfb66b8b5b182aa Mon Sep 17 00:00:00 2001 From: Tom Clegg Date: Tue, 11 Aug 2015 02:26:51 -0400 Subject: [PATCH] 6934: Put wrapper in /lib/security/ instead of writing dist-packages path in config file. Make integration test work. --- sdk/pam/Dockerfile | 27 +++++++++++++++--- sdk/pam/arvados_pam/__init__.py | 14 ++++++---- sdk/pam/pam-configs/arvados | 4 +-- sdk/pam/tests/integration_test.pl | 46 +++++++++++++++++++++++++++++++ sdk/pam/tests/test_integration.py | 19 ++++++++----- 5 files changed, 92 insertions(+), 18 deletions(-) create mode 100755 sdk/pam/tests/integration_test.pl diff --git a/sdk/pam/Dockerfile b/sdk/pam/Dockerfile index c5e83f8462..76b08b9a60 100644 --- a/sdk/pam/Dockerfile +++ b/sdk/pam/Dockerfile @@ -1,8 +1,8 @@ # Manual integration test: # 0. python setup.py sdist rotate --keep=1 --match .tar.gz -# 1. docker build -name arvados:pam_test . -# 2. docker run -it arvados:pam_test -# 3. container# edit /etc/pam.d/login # set api host and shell VM name +# 1. replace 53015 below with your api server's port number +# 2. docker build -name arvados:pam_test . +# 3. docker run -it --add-host zzzzz.arvadosapi.com:"$(hostname -I |awk '{print $1}')" arvados:pam_test # 4. container# useradd testusername # 5. container# login # enter username and token @@ -12,9 +12,28 @@ RUN apt-get -qy dist-upgrade RUN apt-get -qy install python python-virtualenv libpam-python rsyslog # Packages required by pycurl, ciso8601 RUN apt-get -qy install libcurl4-gnutls-dev python2.7-dev + +# for jessie (which also has other snags) +# RUN apt-get -qy install python-pip libgnutls28-dev + RUN pip install --upgrade setuptools RUN pip install python-pam ADD dist /dist RUN pip install /dist/arvados-pam-*.tar.gz + +# Configure and enable the module (hopefully vendor packages will offer a neater way) +RUN perl -pi -e 's{api.example}{zzzzz.arvadosapi.com:53015}; s{shell\.example}{testvm2.shell insecure};' /usr/share/pam-configs/arvados RUN DEBIAN_FRONTEND=noninteractive pam-auth-update arvados --remove unix -CMD rsyslogd & tail -F /var/log/auth.log & bash + +# Add a user account matching the fixture +RUN useradd -ms /bin/bash active + +# Test with python (SIGSEGV during tests) +#ADD . /pam +#WORKDIR /pam +#CMD rsyslogd & tail -F /var/log/auth.log & python setup.py test + +# Test with perl (SIGSEGV when program exits) +RUN apt-get install -qy libauthen-pam-perl +ADD tests/integration_test.pl /integration_test.pl +CMD rsyslogd & tail -F /var/log/auth.log & sleep 1 && /integration_test.pl diff --git a/sdk/pam/arvados_pam/__init__.py b/sdk/pam/arvados_pam/__init__.py index 920a3649d4..1ec6a3a180 100644 --- a/sdk/pam/arvados_pam/__init__.py +++ b/sdk/pam/arvados_pam/__init__.py @@ -28,7 +28,9 @@ class AuthEvent(object): ok = False try: self.api_host = self.config['arvados_api_host'] - self.arv = arvados.api('v1', host=self.api_host, token=self.token, cache=None) + self.arv = arvados.api('v1', host=self.api_host, token=self.token, + insecure=self.config.get('insecure'), + cache=False) vmname = self.config['virtual_machine_hostname'] vms = self.arv.virtual_machines().list(filters=[['hostname','=',vmname]]).execute() @@ -94,18 +96,20 @@ def pam_sm_authenticate(pamh, flags, argv): config = {} config['arvados_api_host'] = argv[1] config['virtual_machine_hostname'] = argv[2] - config['noprompt'] = (len(argv) > 3 and argv[3] == 'noprompt') + if len(argv) > 3: + for k in argv[3:]: + config[k] = True try: - username = pamh.get_user() - except pamh.exception as e: + username = pamh.get_user(None) + except pamh.exception, e: return e.pam_result if not username: return pamh.PAM_USER_UNKNOWN try: - prompt = '' if config['noprompt'] else 'Arvados API token: ' + prompt = '' if config.get('noprompt') else 'Arvados API token: ' token = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, prompt)).resp except pamh.exception as e: return e.pam_result diff --git a/sdk/pam/pam-configs/arvados b/sdk/pam/pam-configs/arvados index 3d245f0b24..6972a396a8 100644 --- a/sdk/pam/pam-configs/arvados +++ b/sdk/pam/pam-configs/arvados @@ -9,6 +9,6 @@ Default: yes Priority: 256 Auth-Type: Primary Auth: - [success=end default=ignore] pam_python.so /usr/local/lib/python2.7/dist-packages/arvados_pam/__init__.py api.example shell.example + [success=end default=ignore] pam_python.so /lib/security/libpam_arvados.py api.example shell.example Auth-Initial: - [success=end default=ignore] pam_python.so /usr/local/lib/python2.7/dist-packages/arvados_pam/__init__.py api.example shell.example + [success=end default=ignore] pam_python.so /lib/security/libpam_arvados.py api.example shell.example diff --git a/sdk/pam/tests/integration_test.pl b/sdk/pam/tests/integration_test.pl new file mode 100755 index 0000000000..e5dff1ea2e --- /dev/null +++ b/sdk/pam/tests/integration_test.pl @@ -0,0 +1,46 @@ +#!/usr/bin/env perl + +$ENV{ARVADOS_API_HOST_INSECURE} = 1; +use Authen::PAM qw(:constants); + +for my $case (['good', 1, 'active', '3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi'], + ['badtoken', 0, 'active', 'badtokenmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi'], + ['badusername', 0, 'baduser', '3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi']) { + dotest(@$case); +} +print "=== OK ===\n"; + +sub dotest { + my ($label, $expect_ok, $user, $token) = @_; + print "$label: "; + my $service_name = 'login'; + $main::Token = $token; + my $pamh = new Authen::PAM($service_name, $user, \&token_conv_func); + ref($pamh) || die "Error code $pamh during PAM init!"; + $pamh->pam_set_item(PAM_RHOST(), '::1'); + $pamh->pam_set_item(PAM_RUSER(), 'none'); + $pamh->pam_set_item(PAM_TTY(), '/dev/null'); + my $flags = PAM_SILENT(); + $res = $pamh->pam_authenticate($flags); + $msg = $pamh->pam_strerror($res); + print "Result (code $res): $msg\n"; + if (($res == 0) != ($expect_ok == 1)) { + die "*** FAIL ***\n"; + } +} + +sub token_conv_func { + my @res; + while ( @_ ) { + my $code = shift; + my $msg = shift; + my $ans; + print "Message (type $code): $msg\n"; + if ($code == PAM_PROMPT_ECHO_OFF() || $code == PAM_PROMPT_ECHO_ON()) { + $ans = $main::Token; + } + push @res, (0,$ans); + } + push @res, PAM_SUCCESS(); + return @res; +} diff --git a/sdk/pam/tests/test_integration.py b/sdk/pam/tests/test_integration.py index 2728bcd1bc..53ef0eacbf 100644 --- a/sdk/pam/tests/test_integration.py +++ b/sdk/pam/tests/test_integration.py @@ -1,8 +1,13 @@ import os -if os.path.exists('/etc/pam.d/arvados-pam-test'): +if os.path.exists('/usr/share/pam-configs/arvados') and os.getuid() == 0: + """These tests assume we are running (in a docker container) with + arvados_pam configured and a test API server running. + """ import pam import unittest + # From services/api/test/fixtures/api_client_authorizations.yml + # because that file is not available during integration tests: ACTIVE_TOKEN = '3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi' SPECTATOR_TOKEN = 'zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu' @@ -11,13 +16,13 @@ if os.path.exists('/etc/pam.d/arvados-pam-test'): self.p = pam.pam() def test_allow(self): - self.assertTrue(self.p.authenticate('active', ACTIVE_TOKEN, service='arvados-pam-test')) + self.assertTrue(self.p.authenticate('active', ACTIVE_TOKEN, service='login')) - def test_deny_service(self): - self.assertFalse(self.p.authenticate('active', ACTIVE_TOKEN, service='login')) + def test_deny_bad_token(self): + self.assertFalse(self.p.authenticate('active', 'thisisaverybadtoken', service='login')) - def test_deny_token(self): - self.assertFalse(self.p.authenticate('active', 'bogustoken', service='arvados-pam-test')) + def test_deny_empty_token(self): + self.assertFalse(self.p.authenticate('active', '', service='login')) def test_deny_permission(self): - self.assertFalse(self.p.authenticate('spectator', SPECTATOR_TOKEN, service='arvados-pam-test')) + self.assertFalse(self.p.authenticate('spectator', SPECTATOR_TOKEN, service='login')) -- 2.30.2