URL: https://github.com/SSSD/sssd/pull/25 Author: jhrozek Title: #25: TESTS: Add integration tests for the proxy provider of sssd-secrets Action: synchronized
To pull the PR as Git branch: git remote add ghsssd https://github.com/SSSD/sssd git fetch ghsssd pull/25/head:pr25 git checkout pr25
From 49e9473b07d7bcd8ee1085f7f32e82a96aa1c537 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek <jhro...@redhat.com> Date: Mon, 8 Aug 2016 17:49:05 +0200 Subject: [PATCH] TESTS: Add integration tests for the sssd-secrets local provider Resolves: https://fedorahosted.org/sssd/ticket/3054 Implements a simple HTTP client and uses it to talk to the sssd-secrets responder. Only the local provider is tested at the moment. --- contrib/ci/deps.sh | 2 + src/tests/intg/Makefile.am | 5 ++ src/tests/intg/config.py.m4 | 3 + src/tests/intg/secrets.py | 138 +++++++++++++++++++++++++++++++++++++++ src/tests/intg/test_secrets.py | 143 +++++++++++++++++++++++++++++++++++++++++ src/util/util.c | 2 +- 6 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 src/tests/intg/secrets.py create mode 100644 src/tests/intg/test_secrets.py diff --git a/contrib/ci/deps.sh b/contrib/ci/deps.sh index 1a94e3d..9a7098c 100644 --- a/contrib/ci/deps.sh +++ b/contrib/ci/deps.sh @@ -45,6 +45,7 @@ if [[ "$DISTRO_BRANCH" == -redhat-* ]]; then pyldb rpm-build uid_wrapper + python-requests ) _DEPS_LIST_SPEC=` sed -e 's/@PACKAGE_VERSION@/0/g' \ @@ -114,6 +115,7 @@ if [[ "$DISTRO_BRANCH" == -debian-* ]]; then python-pytest python-ldap python-ldb + python-requests ldap-utils slapd systemtap-sdt-dev diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am index 75422a4..1e08ead 100644 --- a/src/tests/intg/Makefile.am +++ b/src/tests/intg/Makefile.am @@ -16,6 +16,8 @@ dist_noinst_DATA = \ test_memory_cache.py \ test_ts_cache.py \ test_netgroup.py \ + secrets.py \ + test_secrets.py \ $(NULL) config.py: config.py.m4 @@ -25,6 +27,9 @@ config.py: config.py.m4 -D "pidpath=\`$(pidpath)'" \ -D "logpath=\`$(logpath)'" \ -D "mcpath=\`$(mcpath)'" \ + -D "secdbpath=\`$(secdbpath)'" \ + -D "libexecpath=\`$(libexecdir)'" \ + -D "runstatedir=\`$(runstatedir)'" \ $< > $@ root: diff --git a/src/tests/intg/config.py.m4 b/src/tests/intg/config.py.m4 index 77aa47b..65e17e5 100644 --- a/src/tests/intg/config.py.m4 +++ b/src/tests/intg/config.py.m4 @@ -12,3 +12,6 @@ PID_PATH = "pidpath" PIDFILE_PATH = PID_PATH + "/sssd.pid" LOG_PATH = "logpath" MCACHE_PATH = "mcpath" +SECDB_PATH = "secdbpath" +LIBEXEC_PATH = "libexecpath" +RUNSTATEDIR = "runstatedir" diff --git a/src/tests/intg/secrets.py b/src/tests/intg/secrets.py new file mode 100644 index 0000000..b488225 --- /dev/null +++ b/src/tests/intg/secrets.py @@ -0,0 +1,138 @@ +# +# Secrets responder test client +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import socket +import requests + +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.connection import HTTPConnection +from requests.packages.urllib3.connectionpool import HTTPConnectionPool +from requests.compat import quote, unquote, urlparse + + +class HTTPUnixConnection(HTTPConnection): + def __init__(self, host, timeout=60, **kwargs): + # pylint: disable=bad-super-call + super(HTTPUnixConnection, self).__init__('localhost') + self.unix_socket = host + self.timeout = timeout + + def connect(self): + sock = socket.socket(family=socket.AF_UNIX) + sock.settimeout(self.timeout) + sock.connect(self.unix_socket) + self.sock = sock + + +class HTTPUnixConnectionPool(HTTPConnectionPool): + scheme = 'http+unix' + ConnectionCls = HTTPUnixConnection + + +class HTTPUnixAdapter(HTTPAdapter): + def get_connection(self, url, proxies=None): + # proxies, silently ignored + path = unquote(urlparse(url).netloc) + return HTTPUnixConnectionPool(path) + + +class SecretsHttpClient(object): + secrets_sock_path = '/var/run/secrets.socket' + secrets_container = 'secrets' + + def __init__(self, content_type='application/json', sock_path=None): + if sock_path is None: + sock_path = self.secrets_sock_path + + self.content_type = content_type + self.session = requests.Session() + self.session.mount('http+unix://', HTTPUnixAdapter()) + self.headers = dict({'Content-Type': content_type}) + self.url = 'http+unix://' + \ + quote(sock_path, safe='') + \ + '/' + \ + self.secrets_container + self._last_response = None + + def _join_url(self, resource): + path = self.url.rstrip('/') + '/' + if resource is not None: + path = path + resource.lstrip('/') + return path + + def _add_headers(self, **kwargs): + headers = kwargs.get('headers', None) + if headers is None: + headers = dict() + headers.update(self.headers) + return headers + + def _request(self, cmd, path, **kwargs): + self._last_response = None + url = self._join_url(path) + kwargs['headers'] = self._add_headers(**kwargs) + self._last_response = cmd(url, **kwargs) + return self._last_response + + @property + def last_response(self): + return self._last_response + + def get(self, path, **kwargs): + return self._request(self.session.get, path, **kwargs) + + def list(self, **kwargs): + return self._request(self.session.get, None, **kwargs) + + def put(self, name, **kwargs): + return self._request(self.session.put, name, **kwargs) + + def delete(self, name, **kwargs): + return self._request(self.session.delete, name, **kwargs) + + def post(self, name, **kwargs): + return self._request(self.session.post, name, **kwargs) + + +class SecretsLocalClient(SecretsHttpClient): + def list_secrets(self): + res = self.list() + res.raise_for_status() + simple = res.json() + return simple + + def get_secret(self, name): + res = self.get(name) + res.raise_for_status() + simple = res.json() + ktype = simple.get("type", None) + if ktype != "simple": + raise TypeError("Invalid key type: %s" % ktype) + return simple["value"] + + def set_secret(self, name, value): + res = self.put(name, json={"type": "simple", "value": value}) + res.raise_for_status() + + def del_secret(self, name): + res = self.delete(name) + res.raise_for_status() + + def create_container(self, name): + res = self.post(name) + res.raise_for_status() diff --git a/src/tests/intg/test_secrets.py b/src/tests/intg/test_secrets.py new file mode 100644 index 0000000..6d076e5 --- /dev/null +++ b/src/tests/intg/test_secrets.py @@ -0,0 +1,143 @@ +# +# Secrets responder integration tests +# +# Copyright (c) 2016 Red Hat, Inc. +# +# This is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +import os +import stat +import config +import signal +import subprocess +import time +import pytest +from requests import HTTPError + +from util import unindent +from secrets import SecretsLocalClient + + +def create_conf_fixture(request, contents): + """Generate sssd.conf and add teardown for removing it""" + conf = open(config.CONF_PATH, "w") + conf.write(contents) + conf.close() + os.chmod(config.CONF_PATH, stat.S_IRUSR | stat.S_IWUSR) + request.addfinalizer(lambda: os.unlink(config.CONF_PATH)) + + +def create_sssd_secrets_fixture(request): + if subprocess.call(['sssd', "--genconf"]) != 0: + raise Exception("failed to regenerate confdb") + + resp_path = os.path.join(config.LIBEXEC_PATH, "sssd", "sssd_secrets") + + secpid = os.fork() + if secpid == 0: + if subprocess.call([resp_path, "--uid=0", "--gid=0"]) != 0: + raise Exception("sssd_secrets failed to start") + + time.sleep(1) + + def sec_teardown(): + if secpid == 0: + return + + os.kill(secpid, signal.SIGTERM) + for secdb_file in os.listdir(config.SECDB_PATH): + os.unlink(config.SECDB_PATH + "/" + secdb_file) + request.addfinalizer(sec_teardown) + + +@pytest.fixture +def setup_for_secrets(request): + """ + Just set up the local provider for tests and enable the secrets + responder + """ + conf = unindent("""\ + [sssd] + domains = local + services = nss, pam + + [domain/local] + id_provider=local + """).format(**locals()) + + create_conf_fixture(request, conf) + create_sssd_secrets_fixture(request) + return None + + +@pytest.fixture +def secrets_cli(request): + sock_path = os.path.join(config.RUNSTATEDIR, "secrets.socket") + cli = SecretsLocalClient(sock_path=sock_path) + return cli + + +def test_crd_ops(setup_for_secrets, secrets_cli): + """ + Test that the basic Create, Retrieve, Delete operations work + """ + cli = secrets_cli + + # Listing a totally empty database yields a 404 error, no secrets are there + with pytest.raises(HTTPError) as err404: + secrets = cli.list_secrets() + assert err404.value.message.startswith("404") + + # Set some value, should succeed + cli.set_secret("foo", "bar") + + fooval = cli.get_secret("foo") + assert fooval == "bar" + + # Listing secrets should work now as well + secrets = cli.list_secrets() + assert len(secrets) == 1 + assert "foo" in secrets + + # Overwriting a secret is an error + with pytest.raises(HTTPError) as err409: + cli.set_secret("foo", "baz") + assert err409.value.message.startswith("409") + + # Delete a secret + cli.del_secret("foo") + with pytest.raises(HTTPError) as err404: + fooval = cli.get_secret("foo") + assert err404.value.message.startswith("404") + + # Delete a non-existent secret must yield a 404 + with pytest.raises(HTTPError) as err404: + cli.del_secret("foo") + assert err404.value.message.startswith("404") + + +def test_containers(setup_for_secrets, secrets_cli): + """ + Test that storing secrets inside containers works + """ + cli = secrets_cli + + # No trailing slash, no game.. + with pytest.raises(HTTPError) as err400: + cli.create_container("mycontainer") + assert err400.value.message.startswith("400") + + cli.create_container("mycontainer/") + cli.set_secret("mycontainer/foo", "containedfooval") + assert cli.get_secret("mycontainer/foo") == "containedfooval" diff --git a/src/util/util.c b/src/util/util.c index d4878bf..28b2079 100644 --- a/src/util/util.c +++ b/src/util/util.c @@ -835,7 +835,7 @@ bool check_ipv6_addr(struct in6_addr *addr, uint8_t flags) const char * const * get_known_services(void) { static const char *svc[] = {"nss", "pam", "sudo", "autofs", - "ssh", "pac", "ifp", NULL }; + "ssh", "pac", "ifp", "secrets", NULL }; return svc; }
_______________________________________________ sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org