mirror of
				https://github.com/laurivosandi/certidude
				synced 2025-10-31 01:19:11 +00:00 
			
		
		
		
	tests: Handle forking
This commit is contained in:
		| @@ -20,7 +20,10 @@ script: | ||||
|   - sudo useradd adminbot -G sudo -p '$1$PBkf5waA$n9EV6WJ7PS6lyGWkgeTPf1' | ||||
|   - sudo useradd userbot -G users -p '$1$PBkf5waA$n9EV6WJ7PS6lyGWkgeTPf1' | ||||
|   - sudo adduser --system --no-create-home --group certidude | ||||
|   - sudo py.test -s -v --cov-report xml --cov=certidude tests/ | ||||
|   - sudo coverage run --parallel-mode --source certidude -m py.test tests | ||||
|   - sudo coverage combine | ||||
|   - sudo coverage report | ||||
|   - sudo coverage xml | ||||
| cache: | ||||
|   directories: | ||||
|     - $HOME/.cache/pip | ||||
|   | ||||
							
								
								
									
										12
									
								
								README.rst
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.rst
									
									
									
									
									
								
							| @@ -353,7 +353,17 @@ To install the package from the source: | ||||
|  | ||||
| .. code:: bash | ||||
|  | ||||
|     python setup.py  install --single-version-externally-managed --root / | ||||
|     pip install -e . | ||||
|  | ||||
| To run tests and measure code coverage grab a clean VM or container: | ||||
|  | ||||
| .. code:: bash | ||||
|  | ||||
|     pip install codecov pytest-cov | ||||
|     rm .coverage* | ||||
|     TRAVIS=1 coverage run --parallel-mode --source certidude -m py.test tests | ||||
|     coverage combine | ||||
|     coverage report | ||||
|  | ||||
| To uninstall: | ||||
|  | ||||
|   | ||||
| @@ -17,18 +17,6 @@ from certidude import const, config | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
|  | ||||
| class CertificateStatusResource(object): | ||||
|     """ | ||||
|     openssl ocsp -issuer CAcert_class1.pem -serial 0x<serial no in hex> -url http://localhost -CAfile cacert_both.pem | ||||
|     """ | ||||
|     def on_post(self, req, resp): | ||||
|         ocsp_request = req.stream.read(req.content_length) | ||||
|         for component in decoder.decode(ocsp_request): | ||||
|             click.echo(component) | ||||
|         resp.append_header("Content-Type", "application/ocsp-response") | ||||
|         resp.status = falcon.HTTP_200 | ||||
|         raise NotImplementedError() | ||||
|  | ||||
|  | ||||
| class CertificateAuthorityResource(object): | ||||
|     def on_get(self, req, resp): | ||||
| @@ -195,7 +183,6 @@ def certidude_app(log_handlers=[]): | ||||
|     app.req_options.auto_parse_form_urlencoded = True | ||||
|  | ||||
|     # Certificate authority API calls | ||||
|     app.add_route("/api/ocsp/", CertificateStatusResource()) | ||||
|     app.add_route("/api/certificate/", CertificateAuthorityResource()) | ||||
|     app.add_route("/api/revoked/", RevocationListResource()) | ||||
|     app.add_route("/api/signed/{cn}/", SignedCertificateDetailResource()) | ||||
|   | ||||
| @@ -96,7 +96,7 @@ def signer_exec(cmd, *bits): | ||||
|     sock.sendall(b"\n\n") | ||||
|     buf = sock.recv(8192) | ||||
|     if not buf: | ||||
|         raise | ||||
|         raise Exception("Connection lost") | ||||
|     return buf | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -15,8 +15,9 @@ import subprocess | ||||
| import sys | ||||
| from configparser import ConfigParser, NoOptionError, NoSectionError | ||||
| from certidude.helpers import certidude_request_certificate | ||||
| from certidude.common import expand_paths, ip_address, ip_network, apt, rpm, pip | ||||
| from certidude.common import expand_paths, ip_address, ip_network, apt, rpm, pip, drop_privileges | ||||
| from datetime import datetime, timedelta | ||||
| from time import sleep | ||||
| import const | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
| @@ -815,9 +816,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | ||||
|     click.echo("Using templates from %s" % template_path) | ||||
|  | ||||
|     if not directory: | ||||
|         if os.getuid(): | ||||
|             directory = os.path.join(os.path.expanduser("~/.certidude"), common_name) | ||||
|         else: | ||||
|         directory = os.path.join("/var/lib/certidude", common_name) | ||||
|     click.echo("Placing authority files in %s" % directory) | ||||
|  | ||||
| @@ -831,7 +829,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | ||||
|     ca_key = os.path.join(directory, "ca_key.pem") | ||||
|     ca_crt = os.path.join(directory, "ca_crt.pem") | ||||
|  | ||||
|     if os.getuid() == 0: | ||||
|     try: | ||||
|         pwd.getpwnam("certidude") | ||||
|         click.echo("User 'certidude' already exists") | ||||
| @@ -900,8 +897,6 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, | ||||
|  | ||||
|     _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") | ||||
|     os.setgid(gid) | ||||
|     else: | ||||
|         click.echo("Not root, skipping user and system config creation") | ||||
|  | ||||
|     if not os.path.exists(const.CONFIG_DIR): | ||||
|         click.echo("Creating %s" % const.CONFIG_DIR) | ||||
| @@ -1121,6 +1116,7 @@ def certidude_list(verbose, show_key_type, show_extensions, show_path, show_sign | ||||
| @click.argument("common_name") | ||||
| @click.option("--overwrite", "-o", default=False, is_flag=True, help="Revoke valid certificate with same CN") | ||||
| def certidude_sign(common_name, overwrite): | ||||
|     drop_privileges() | ||||
|     from certidude import authority | ||||
|     cert = authority.sign(common_name, overwrite) | ||||
|  | ||||
| @@ -1128,6 +1124,7 @@ def certidude_sign(common_name, overwrite): | ||||
| @click.command("revoke", help="Revoke certificate") | ||||
| @click.argument("common_name") | ||||
| def certidude_revoke(common_name): | ||||
|     drop_privileges() | ||||
|     from certidude import authority | ||||
|     authority.revoke(common_name) | ||||
|  | ||||
| @@ -1144,6 +1141,7 @@ def certidude_cron(): | ||||
|             os.rename(path, expired_path) | ||||
|             click.echo("Moved %s to %s" % (path, expired_path)) | ||||
|  | ||||
|  | ||||
| @click.command("serve", help="Run server") | ||||
| @click.option("-p", "--port", default=80, help="Listen port") | ||||
| @click.option("-l", "--listen", default="0.0.0.0", help="Listen address") | ||||
| @@ -1151,9 +1149,10 @@ def certidude_cron(): | ||||
| def certidude_serve(port, listen, fork): | ||||
|     from setproctitle import setproctitle | ||||
|     from certidude.signer import SignServer | ||||
|     from certidude import const | ||||
|     from certidude import authority, const | ||||
|     click.echo("Using configuration from: %s" % const.CONFIG_PATH) | ||||
|  | ||||
|  | ||||
|     log_handlers = [] | ||||
|  | ||||
|     from certidude import config | ||||
| @@ -1166,10 +1165,7 @@ def certidude_serve(port, listen, fork): | ||||
|  | ||||
|     # TODO: umask! | ||||
|  | ||||
|     import pwd | ||||
|     _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") | ||||
|     restricted_groups = [] | ||||
|     restricted_groups.append(gid) | ||||
|  | ||||
|     from logging.handlers import RotatingFileHandler | ||||
|     rh = RotatingFileHandler("/var/log/certidude.log", maxBytes=1048576*5, backupCount=5) | ||||
|     rh.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) | ||||
| @@ -1180,11 +1176,10 @@ def certidude_serve(port, listen, fork): | ||||
|     Spawn signer process | ||||
|     """ | ||||
|  | ||||
|     child_pid = os.fork() | ||||
|     if os.path.exists(const.SIGNER_SOCKET_PATH): | ||||
|         os.unlink(const.SIGNER_SOCKET_PATH) | ||||
|  | ||||
|     if child_pid: | ||||
|         pass | ||||
|     else: | ||||
|     if not os.fork(): | ||||
|         click.echo("Signer process spawned with PID %d at %s" % (os.getpid(), const.SIGNER_SOCKET_PATH)) | ||||
|         setproctitle("[signer]") | ||||
|  | ||||
| @@ -1199,7 +1194,7 @@ def certidude_serve(port, listen, fork): | ||||
|         server = SignServer() | ||||
|  | ||||
|         # Drop privileges | ||||
|         if not os.getuid(): | ||||
|         _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") | ||||
|         os.chown(const.SIGNER_SOCKET_PATH, uid, gid) | ||||
|         os.chmod(const.SIGNER_SOCKET_PATH, 0770) | ||||
|  | ||||
| @@ -1208,12 +1203,21 @@ def certidude_serve(port, listen, fork): | ||||
|         os.setgroups([]) | ||||
|         os.setgid(gid) | ||||
|         os.setuid(uid) | ||||
|         else: | ||||
|             click.echo("Not dropping privileges of signer process") | ||||
|  | ||||
|         try: | ||||
|             asyncore.loop() | ||||
|         except asyncore.ExitNow: | ||||
|             pass | ||||
|         click.echo("Signer was shut down") | ||||
|         return | ||||
|  | ||||
|     click.echo("Waiting for signer to start up") | ||||
|     time_left = 2.0 | ||||
|     delay = 0.1 | ||||
|     while not os.path.exists(const.SIGNER_SOCKET_PATH) and time_left > 0: | ||||
|         sleep(delay) | ||||
|         time_left -= delay | ||||
|     assert authority.signer_exec("ping") == "pong" | ||||
|     click.echo("Signer alive") | ||||
|  | ||||
|     click.echo("Users subnets: %s" % | ||||
|         ", ".join([str(j) for j in config.USER_SUBNETS])) | ||||
| @@ -1230,7 +1234,7 @@ def certidude_serve(port, listen, fork): | ||||
|  | ||||
|     click.echo("Serving API at %s:%d" % (listen, port)) | ||||
|     from wsgiref.simple_server import make_server, WSGIServer | ||||
|     from SocketServer import ThreadingMixIn, ForkingMixIn | ||||
|     from SocketServer import ForkingMixIn | ||||
|     from certidude.api import certidude_app | ||||
|  | ||||
|     class ThreadingWSGIServer(ForkingMixIn, WSGIServer): | ||||
| @@ -1251,13 +1255,6 @@ def certidude_serve(port, listen, fork): | ||||
|     if os.path.exists("/etc/cron.hourly/certidude"): | ||||
|         os.system("/etc/cron.hourly/certidude") | ||||
|  | ||||
|     # PAM needs access to /etc/shadow | ||||
|     if config.AUTHENTICATION_BACKENDS == {"pam"}: | ||||
|         import grp | ||||
|         name, passwd, num, mem = grp.getgrnam("shadow") | ||||
|         click.echo("Adding current user to shadow group due to PAM authentication backend") | ||||
|         restricted_groups.append(num) | ||||
|  | ||||
|     if config.EVENT_SOURCE_PUBLISH: | ||||
|         from certidude.push import EventSourceLogHandler | ||||
|         log_handlers.append(EventSourceLogHandler()) | ||||
| @@ -1281,17 +1278,13 @@ def certidude_serve(port, listen, fork): | ||||
|         atexit.register(exit_handler) | ||||
|         logger.debug("Started Certidude at %s", const.FQDN) | ||||
|  | ||||
|         drop_privileges() | ||||
|  | ||||
|         # Drop privileges | ||||
|         os.setgroups(restricted_groups) | ||||
|         os.setgid(gid) | ||||
|         os.setuid(uid) | ||||
|  | ||||
|         click.echo("Switched to user %s (uid=%d, gid=%d); member of groups %s" % | ||||
|             ("certidude", os.getuid(), os.getgid(), ", ".join([str(j) for j in os.getgroups()]))) | ||||
|  | ||||
|         os.umask(0o007) | ||||
|  | ||||
|         def quit_handler(*args, **kwargs): | ||||
|             click.echo("Shutting down HTTP server...") | ||||
|             import threading | ||||
|             threading.Thread(target=httpd.shutdown).start() | ||||
|         signal.signal(signal.SIGHUP, quit_handler) | ||||
|         httpd.serve_forever() | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,27 @@ import os | ||||
| import click | ||||
| import subprocess | ||||
|  | ||||
| def drop_privileges(): | ||||
|     from certidude import config | ||||
|     import pwd | ||||
|     _, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude") | ||||
|     restricted_groups = [] | ||||
|     restricted_groups.append(gid) | ||||
|  | ||||
|     # PAM needs access to /etc/shadow | ||||
|     if config.AUTHENTICATION_BACKENDS == {"pam"}: | ||||
|         import grp | ||||
|         name, passwd, num, mem = grp.getgrnam("shadow") | ||||
|         click.echo("Adding current user to shadow group due to PAM authentication backend") | ||||
|         restricted_groups.append(num) | ||||
|  | ||||
|     os.setgroups(restricted_groups) | ||||
|     os.setgid(gid) | ||||
|     os.setuid(uid) | ||||
|     click.echo("Switched to user %s (uid=%d, gid=%d); member of groups %s" % | ||||
|         ("certidude", os.getuid(), os.getgid(), ", ".join([str(j) for j in os.getgroups()]))) | ||||
|     os.umask(0o007) | ||||
|  | ||||
| def ip_network(j): | ||||
|     import ipaddress | ||||
|     return ipaddress.ip_network(unicode(j)) | ||||
|   | ||||
| @@ -6,16 +6,15 @@ import sys | ||||
|  | ||||
| KEY_SIZE = 1024 if os.getenv("TRAVIS") else 4096 | ||||
| RUN_DIR = "/run/certidude" | ||||
| CONFIG_DIR = os.path.expanduser("~/.certidude") if os.getuid() else "/etc/certidude" | ||||
| CONFIG_DIR = "/etc/certidude" | ||||
| CONFIG_PATH = os.path.join(CONFIG_DIR, "server.conf") | ||||
|  | ||||
| CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.conf") | ||||
| SERVICES_CONFIG_PATH = os.path.join(CONFIG_DIR, "services.conf") | ||||
| SERVER_PID_PATH = os.path.join(CONFIG_DIR if os.getuid() else RUN_DIR, "server.pid") | ||||
| SERVER_LOG_PATH = os.path.join(CONFIG_DIR, "server.log") if os.getuid() else "/var/log/certidude-server.log" | ||||
| SIGNER_SOCKET_PATH = os.path.join(CONFIG_DIR, "signer.sock") if os.getuid() else "/run/certidude/signer.sock" | ||||
| SIGNER_PID_PATH = os.path.join(CONFIG_DIR if os.getuid() else RUN_DIR, "signer.pid") | ||||
| SIGNER_LOG_PATH = os.path.join(CONFIG_DIR, "signer.log") if os.getuid() else "/var/log/certidude-signer.log" | ||||
| SERVER_PID_PATH = os.path.join(RUN_DIR, "server.pid") | ||||
| SERVER_LOG_PATH = "/var/log/certidude-server.log" | ||||
| SIGNER_SOCKET_PATH = "/run/certidude/signer.sock" | ||||
| SIGNER_PID_PATH = os.path.join(RUN_DIR, "signer.pid") | ||||
| SIGNER_LOG_PATH = "/var/log/certidude-signer.log" | ||||
|  | ||||
| try: | ||||
|     FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3] | ||||
|   | ||||
| @@ -55,8 +55,14 @@ class SignHandler(asynchat.async_chat): | ||||
|  | ||||
|             self.send(crl.public_bytes(Encoding.PEM)) | ||||
|  | ||||
|         elif cmd == "ocsp-request": | ||||
|             NotImplemented # TODO: Implement OCSP | ||||
|         elif cmd == "ping": | ||||
|             self.send("pong") | ||||
|             self.close() | ||||
|  | ||||
|         elif cmd == "exit": | ||||
|             self.send("ok") | ||||
|             self.close() | ||||
|             raise asyncore.ExitNow() | ||||
|  | ||||
|         elif cmd == "sign-request": | ||||
|             # Only common name and public key are used from request | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import subprocess | ||||
| import pwd | ||||
| from click.testing import CliRunner | ||||
| from datetime import datetime, timedelta | ||||
| from time import sleep | ||||
| import pytest | ||||
|  | ||||
| # pkill py && rm -Rfv ~/.certidude && TRAVIS=1 py.test tests | ||||
| import shutil | ||||
| import os | ||||
|  | ||||
| runner = CliRunner() | ||||
|  | ||||
| @@ -33,8 +33,11 @@ def generate_csr(cn=None): | ||||
|     return buf | ||||
|  | ||||
| def test_cli_setup_authority(): | ||||
|     import shutil | ||||
|     import os | ||||
|     import sys | ||||
|  | ||||
|     assert os.getuid() == 0, "Run tests as root in a clean VM or container" | ||||
|  | ||||
|     if os.path.exists("/run/certidude/signer.pid"): | ||||
|         with open("/run/certidude/signer.pid") as fh: | ||||
|             try: | ||||
| @@ -69,7 +72,12 @@ def test_cli_setup_authority(): | ||||
|     from certidude import const | ||||
|  | ||||
|     result = runner.invoke(cli, ['setup', 'authority']) | ||||
|     os.setgid(0) # Restore GID | ||||
|     os.umask(0022) | ||||
|  | ||||
|     assert not result.exception, result.output | ||||
|     assert os.getuid() == 0 and os.getgid() == 0, "Serve dropped permissions incorrectly!" | ||||
|  | ||||
|  | ||||
|     from certidude import config, authority | ||||
|     assert authority.ca_cert.serial_number >= 0x100000000000000000000000000000000000000 | ||||
| @@ -79,19 +87,20 @@ def test_cli_setup_authority(): | ||||
|  | ||||
|     # Start server before any signing operations are performed | ||||
|     config.CERTIFICATE_RENEWAL_ALLOWED = True | ||||
|     result = runner.invoke(cli, ['serve', '-f', '-p', '80', '-l', '127.0.1.1']) | ||||
|  | ||||
|     server_pid = os.fork() | ||||
|     if not server_pid: | ||||
|         # Fork to prevent umask, setuid, setgid side effects | ||||
|         result = runner.invoke(cli, ['serve', '-p', '80', '-l', '127.0.1.1']) | ||||
|         assert not result.exception, result.output | ||||
|         return | ||||
|  | ||||
|     sleep(1) # Wait for serve to start up | ||||
|  | ||||
|     import requests | ||||
|  | ||||
|     # Test CA certificate fetch | ||||
|     buf = open("/var/lib/certidude/ca.example.lan/ca_crt.pem").read() | ||||
|  | ||||
|     r = client().simulate_get("/api/certificate") | ||||
|     assert r.status_code == 200 | ||||
|     assert r.headers.get('content-type') == "application/x-x509-ca-cert" | ||||
|     assert r.text == buf | ||||
|  | ||||
|     r = requests.get("http://ca.example.lan/api/certificate") | ||||
|     assert r.status_code == 200 | ||||
|     assert r.headers.get('content-type') == "application/x-x509-ca-cert" | ||||
| @@ -107,7 +116,7 @@ def test_cli_setup_authority(): | ||||
|  | ||||
|     # Check that we can retrieve empty CRL | ||||
|     assert authority.export_crl(), "Failed to export CRL" | ||||
|     r = client().simulate_get("/api/revoked/") | ||||
|     r = requests.get("http://ca.example.lan/api/revoked/") | ||||
|     assert r.status_code == 200, r.text | ||||
|  | ||||
|  | ||||
| @@ -116,13 +125,10 @@ def test_cli_setup_authority(): | ||||
|     assert not result.exception, result.output | ||||
|  | ||||
|     # Test static | ||||
|     r = client().simulate_get("/nonexistant.html") | ||||
|     assert r.status_code == 404, r.text | ||||
|     r = client().simulate_get("/index.html") | ||||
|     assert r.status_code == 200, r.text | ||||
|     r = requests.get("http://ca.example.lan/index.html") | ||||
|     assert r.status_code == 200, "server responded %s, logs say %s" % (r.text, open("/var/log/certidude.log").read()) | ||||
|  | ||||
|     assert r.status_code == 200, r.text # if this breaks certidude serve has no read access to static folder | ||||
|     r = requests.get("http://ca.example.lan/nonexistant.html") | ||||
|     assert r.status_code == 404, r.text | ||||
|  | ||||
|     # Test request submission | ||||
|     buf = generate_csr(cn=u"test") | ||||
| @@ -141,7 +147,7 @@ def test_cli_setup_authority(): | ||||
|     assert r.status_code == 202 # already exists, same keypair so it's ok | ||||
|  | ||||
|     r = client().simulate_post("/api/request/", | ||||
|         query_string="wait=1", | ||||
|         query_string="wait=true", | ||||
|         body=buf, | ||||
|         headers={"content-type":"application/pkcs10"}) | ||||
|     assert r.status_code == 303 # redirect to long poll | ||||
| @@ -165,7 +171,8 @@ def test_cli_setup_authority(): | ||||
|     r = client().simulate_get("/api/request/nonexistant/", headers={"Accept":"application/json"}) | ||||
|     assert r.status_code == 404 # nonexistant common names | ||||
|  | ||||
|     r = client().simulate_post("/api/request/", query_string="autosign=1", | ||||
|     r = client().simulate_post("/api/request/", | ||||
|         query_string="autosign=1", | ||||
|         body=buf, | ||||
|         headers={"content-type":"application/pkcs10"}) | ||||
|     assert r.status_code == 200 # autosign successful | ||||
| @@ -176,17 +183,16 @@ def test_cli_setup_authority(): | ||||
|     # Test command line interface | ||||
|     result = runner.invoke(cli, ['list', '-srv']) | ||||
|     assert not result.exception, result.output | ||||
|  | ||||
|     # Some commands have side effects (setuid, setgid etc) | ||||
|     child_pid = os.fork() | ||||
|     if not child_pid: | ||||
|         result = runner.invoke(cli, ['sign', 'test', '-o']) | ||||
|         assert not result.exception, result.output | ||||
|     result = runner.invoke(cli, ['revoke', 'test']) | ||||
|     assert not result.exception, result.output | ||||
|     authority.generate_ovpn_bundle(u"test2") | ||||
|     authority.generate_pkcs12_bundle(u"test3") | ||||
|     result = runner.invoke(cli, ['list', '-srv']) | ||||
|     assert not result.exception, result.output | ||||
|     result = runner.invoke(cli, ['cron']) | ||||
|     assert not result.exception, result.output | ||||
|  | ||||
|         return | ||||
|     else: | ||||
|         os.waitpid(child_pid, 0) | ||||
|     assert os.getuid() == 0 and os.getgid() == 0, "Serve dropped permissions incorrectly!" | ||||
|  | ||||
|     # Test session API call | ||||
|     r = client().simulate_get("/api/", headers={"Authorization":usertoken}) | ||||
| @@ -203,120 +209,110 @@ def test_cli_setup_authority(): | ||||
|     r = client().simulate_get("/api/signed/nonexistant/") | ||||
|     assert r.status_code == 404, r.text | ||||
|  | ||||
|     r = client().simulate_get("/api/signed/test2/") | ||||
|     r = client().simulate_get("/api/signed/test/") | ||||
|     assert r.status_code == 200, r.text | ||||
|     assert r.headers.get('content-type') == "application/x-pem-file" | ||||
|  | ||||
|     r = client().simulate_get("/api/signed/test2/", headers={"Accept":"application/json"}) | ||||
|     r = client().simulate_get("/api/signed/test/", headers={"Accept":"application/json"}) | ||||
|     assert r.status_code == 200, r.text | ||||
|     assert r.headers.get('content-type') == "application/json" | ||||
|  | ||||
|     r = client().simulate_get("/api/signed/test2/", headers={"Accept":"text/plain"}) | ||||
|     r = client().simulate_get("/api/signed/test/", headers={"Accept":"text/plain"}) | ||||
|     assert r.status_code == 415, r.text | ||||
|  | ||||
|     # Test revocations API call | ||||
|     r = client().simulate_get("/api/revoked/", | ||||
|         headers={"Accept":"application/x-pem-file"}) | ||||
|     assert r.status_code == 200, r.text | ||||
|     assert r.headers.get('content-type') == "application/x-pem-file" | ||||
|  | ||||
|     r = requests.get("http://ca.example.lan/api/revoked/", | ||||
|         headers={"Accept":"application/x-pem-file"}) | ||||
|     assert r.status_code == 200, "Server responded with %s, server logs say %s" % (r.text, open("/var/log/certidude.log").read()) | ||||
|     assert r.status_code == 200, r.text # if this breaks certidude serve has no access to signer socket | ||||
|     assert r.headers.get('content-type') == "application/x-pem-file" | ||||
|  | ||||
|     r = client().simulate_get("/api/revoked/") | ||||
|     assert r.status_code == 200, r.text | ||||
|     assert r.headers.get('content-type') == "application/x-pkcs7-crl" | ||||
|  | ||||
|     r = requests.get("http://ca.example.lan/api/revoked/") | ||||
|     assert r.status_code == 200, r.text | ||||
|     assert r.headers.get('content-type') == "application/x-pkcs7-crl" | ||||
|  | ||||
|     r = client().simulate_get("/api/revoked/", | ||||
|         headers={"Accept":"text/plain"}) | ||||
|     assert r.status_code == 415, r.text | ||||
|  | ||||
|     r = client().simulate_get("/api/revoked/", query_string="wait=true", | ||||
|     r = client().simulate_get("/api/revoked/", | ||||
|         query_string="wait=true", | ||||
|         headers={"Accept":"application/x-pem-file"}) | ||||
|     assert r.status_code == 303, r.text | ||||
|  | ||||
|     # Test attribute fetching API call | ||||
|     r = client().simulate_get("/api/signed/test2/attr/") | ||||
|     r = client().simulate_get("/api/signed/test/attr/") | ||||
|     assert r.status_code == 403, r.text | ||||
|     r = client().simulate_get("/api/signed/test2/lease/", headers={"Authorization":admintoken}) | ||||
|     r = client().simulate_get("/api/signed/test/lease/", headers={"Authorization":admintoken}) | ||||
|     assert r.status_code == 404, r.text | ||||
|  | ||||
|     # Insert lease as if VPN gateway had submitted it | ||||
|     path, _, _ = authority.get_signed("test2") | ||||
|     path, _, _ = authority.get_signed("test") | ||||
|     from xattr import setxattr | ||||
|     setxattr(path, "user.lease.address", b"127.0.0.1") | ||||
|     setxattr(path, "user.lease.last_seen", b"random") | ||||
|     r = client().simulate_get("/api/signed/test2/attr/") | ||||
|     r = client().simulate_get("/api/signed/test/attr/") | ||||
|     assert r.status_code == 200, r.text | ||||
|  | ||||
|     # Test lease retrieval | ||||
|     r = client().simulate_get("/api/signed/test2/lease/") | ||||
|     r = client().simulate_get("/api/signed/test/lease/") | ||||
|     assert r.status_code == 401, r.text | ||||
|     r = client().simulate_get("/api/signed/test2/lease/", headers={"Authorization":usertoken}) | ||||
|     r = client().simulate_get("/api/signed/test/lease/", headers={"Authorization":usertoken}) | ||||
|     assert r.status_code == 403, r.text | ||||
|     r = client().simulate_get("/api/signed/test2/lease/", headers={"Authorization":admintoken}) | ||||
|     r = client().simulate_get("/api/signed/test/lease/", headers={"Authorization":admintoken}) | ||||
|     assert r.status_code == 200, r.text | ||||
|     assert r.headers.get('content-type') == "application/json; charset=UTF-8" | ||||
|  | ||||
|  | ||||
|     # Tags should not be visible anonymously | ||||
|     r = client().simulate_get("/api/signed/test2/tag/") | ||||
|     r = client().simulate_get("/api/signed/test/tag/") | ||||
|     assert r.status_code == 401, r.text | ||||
|     r = client().simulate_get("/api/signed/test2/tag/", headers={"Authorization":usertoken}) | ||||
|     r = client().simulate_get("/api/signed/test/tag/", headers={"Authorization":usertoken}) | ||||
|     assert r.status_code == 403, r.text | ||||
|     r = client().simulate_get("/api/signed/test2/tag/", headers={"Authorization":admintoken}) | ||||
|     r = client().simulate_get("/api/signed/test/tag/", headers={"Authorization":admintoken}) | ||||
|     assert r.status_code == 200, r.text | ||||
|  | ||||
|     # Tags can be added only by admin | ||||
|     r = client().simulate_post("/api/signed/test2/tag/") | ||||
|     r = client().simulate_post("/api/signed/test/tag/") | ||||
|     assert r.status_code == 401, r.text | ||||
|     r = client().simulate_post("/api/signed/test2/tag/", | ||||
|     r = client().simulate_post("/api/signed/test/tag/", | ||||
|         headers={"Authorization":usertoken}) | ||||
|     assert r.status_code == 403, r.text | ||||
|     r = client().simulate_post("/api/signed/test2/tag/", | ||||
|     r = client().simulate_post("/api/signed/test/tag/", | ||||
|         body="key=other&value=something", | ||||
|         headers={"content-type": "application/x-www-form-urlencoded", "Authorization":admintoken}) | ||||
|     assert r.status_code == 200, r.text | ||||
|  | ||||
|     # Tags can be overwritten only by admin | ||||
|     r = client().simulate_put("/api/signed/test2/tag/other/") | ||||
|     r = client().simulate_put("/api/signed/test/tag/other/") | ||||
|     assert r.status_code == 401, r.text | ||||
|     r = client().simulate_put("/api/signed/test2/tag/other/", | ||||
|     r = client().simulate_put("/api/signed/test/tag/other/", | ||||
|         headers={"Authorization":usertoken}) | ||||
|     assert r.status_code == 403, r.text | ||||
|     r = client().simulate_put("/api/signed/test2/tag/other/", | ||||
|     r = client().simulate_put("/api/signed/test/tag/other/", | ||||
|         body="value=else", | ||||
|         headers={"content-type": "application/x-www-form-urlencoded", "Authorization":admintoken}) | ||||
|     assert r.status_code == 200, r.text | ||||
|  | ||||
|     # Tags can be deleted only by admin | ||||
|     r = client().simulate_delete("/api/signed/test2/tag/else/") | ||||
|     r = client().simulate_delete("/api/signed/test/tag/else/") | ||||
|     assert r.status_code == 401, r.text | ||||
|     r = client().simulate_delete("/api/signed/test2/tag/else/", | ||||
|     r = client().simulate_delete("/api/signed/test/tag/else/", | ||||
|         headers={"Authorization":usertoken}) | ||||
|     assert r.status_code == 403, r.text | ||||
|     r = client().simulate_delete("/api/signed/test2/tag/else/", | ||||
|     r = client().simulate_delete("/api/signed/test/tag/else/", | ||||
|         headers={"content-type": "application/x-www-form-urlencoded", "Authorization":admintoken}) | ||||
|     assert r.status_code == 200, r.text | ||||
|  | ||||
|  | ||||
|     # Test revocation | ||||
|     r = client().simulate_delete("/api/signed/test2/") | ||||
|     r = client().simulate_delete("/api/signed/test/") | ||||
|     assert r.status_code == 401, r.text | ||||
|     r = client().simulate_delete("/api/signed/test2/", | ||||
|     r = client().simulate_delete("/api/signed/test/", | ||||
|         headers={"Authorization":usertoken}) | ||||
|     assert r.status_code == 403, r.text | ||||
|     r = client().simulate_delete("/api/signed/test2/", | ||||
|     r = client().simulate_delete("/api/signed/test/", | ||||
|         headers={"Authorization":admintoken}) | ||||
|     assert r.status_code == 200, r.text | ||||
|     result = runner.invoke(cli, ['revoke', 'test3']) | ||||
|     assert not result.exception, result.output | ||||
|  | ||||
|  | ||||
|     # Log can be read only by admin | ||||
| @@ -351,7 +347,8 @@ def test_cli_setup_authority(): | ||||
|         query_string="u=userbot&t=1493184342&c=ac9b71421d5741800c5a4905b20c1072594a2df863e60ba836464888786bf2a6", | ||||
|         headers={"content-type": "application/x-www-form-urlencoded", "Authorization":admintoken}) | ||||
|     assert r2.status_code == 403 # invalid checksum | ||||
|     r2 = client().simulate_get("/api/token/", query_string=r.content, | ||||
|     r2 = client().simulate_get("/api/token/", | ||||
|         query_string=r.content, | ||||
|         headers={"User-Agent":"Mozilla/5.0 (X11; Fedora; Linux x86_64) " | ||||
|             "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36"}) | ||||
|     assert r2.status_code == 200 # token consumed by anyone on Fedora | ||||
| @@ -362,7 +359,6 @@ def test_cli_setup_authority(): | ||||
|     assert r2.status_code == 200 # token consumed by anyone on unknown device | ||||
|     assert r2.headers.get('content-type') == "application/x-pkcs12" | ||||
|  | ||||
|  | ||||
|     result = runner.invoke(cli, ['setup', 'openvpn', 'server', "-cn", "vpn.example.lan", "ca.example.lan"]) | ||||
|     assert not result.exception, result.output | ||||
|  | ||||
| @@ -379,9 +375,39 @@ def test_cli_setup_authority(): | ||||
|     # pregen dhparam | ||||
|     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||
|     assert not result.exception, "server responded %s, server logs say %s"  % (result.output, open("/var/log/certidude.log").read()) | ||||
|  | ||||
|     child_pid = os.fork() | ||||
|     if not child_pid: | ||||
|         result = runner.invoke(cli, ['sign', 'vpn.example.lan']) | ||||
|         assert not result.exception, result.output | ||||
|         return | ||||
|     else: | ||||
|         os.waitpid(child_pid, 0) | ||||
|  | ||||
|     result = runner.invoke(cli, ["request", "--no-wait"]) | ||||
|     assert not result.exception, result.output | ||||
|     result = runner.invoke(cli, ["request", "--renew"]) | ||||
|     assert not result.exception, result.output | ||||
|  | ||||
|     # Test revocation on command-line | ||||
|     child_pid = os.fork() | ||||
|     if not child_pid: | ||||
|         result = runner.invoke(cli, ['revoke', 'vpn.example.lan']) | ||||
|         assert not result.exception, result.output | ||||
|         return | ||||
|     else: | ||||
|         os.waitpid(child_pid, 0) | ||||
|  | ||||
|     result = runner.invoke(cli, ['list', '-srv']) | ||||
|     assert not result.exception, result.output | ||||
|     result = runner.invoke(cli, ['cron']) | ||||
|     assert not result.exception, result.output | ||||
|  | ||||
|     # Shut down signer | ||||
|     assert authority.signer_exec("exit") == "ok" | ||||
|  | ||||
|     # Shut down server | ||||
|     with open("/run/certidude/server.pid") as fh: | ||||
|         os.kill(int(fh.read()), 1) | ||||
|  | ||||
|     os.waitpid(server_pid, 0) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user