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 a3e80a6a6dae8cae55abaa5b0a552c778d1282ad 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

Implements a simple HTTP client and uses it to talk to the sssd-secrets
responder. Only the local provider is tested at the moment.

Resolves:
https://fedorahosted.org/sssd/ticket/3054
---
 contrib/ci/deps.sh             |   2 +
 src/tests/intg/Makefile.am     |   5 ++
 src/tests/intg/config.py.m4    |   3 +
 src/tests/intg/secrets.py      | 137 ++++++++++++++++++++++++++++++++++
 src/tests/intg/test_secrets.py | 162 +++++++++++++++++++++++++++++++++++++++++
 5 files changed, 309 insertions(+)
 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..5d4c0e2
--- /dev/null
+++ b/src/tests/intg/secrets.py
@@ -0,0 +1,137 @@
+#
+# 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):
+        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..ed36571
--- /dev/null
+++ b/src/tests/intg/test_secrets.py
@@ -0,0 +1,162 @@
+#
+# 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
+import socket
+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")
+
+    sock_path = os.path.join(config.RUNSTATEDIR, "secrets.socket")
+    sck = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+    for i in xrange(1, 5):
+        try:
+            sck.connect(sock_path)
+        except:
+            time.sleep(0.1)
+        else:
+            break;
+    sck.close()
+
+    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"
+
+    # Removing a non-empty container should not succeed
+    with pytest.raises(HTTPError) as err409:
+        cli.del_secret("mycontainer/")
+    assert err409.value.message.startswith("409")
+
+    # Try removing the secret first, then the container
+    cli.del_secret("mycontainer/foo")
+    cli.del_secret("mycontainer/")
_______________________________________________
sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org
To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org

Reply via email to