diff --git a/certidude/api/lease.py b/certidude/api/lease.py index 8485cec..20555c0 100644 --- a/certidude/api/lease.py +++ b/certidude/api/lease.py @@ -29,12 +29,12 @@ class LeaseResource(object): # TODO: verify signature common_name = req.get_param("client", required=True) path, buf, cert = authority.get_signed(common_name) # TODO: catch exceptions - if cert.serial != req.get_param_as_int("serial", required=True): # Badum we have OCSP! - raise # TODO proper exception - if req.get_param("action") == "client-connect": - xattr.setxattr(path, "user.lease.address", req.get_param("address", required=True).encode("ascii")) - xattr.setxattr(path, "user.lease.last_seen", datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z") - push.publish("lease-update", common_name) + if cert.serial != req.get_param_as_int("serial"): # OCSP-ish solution for OpenVPN, not exposed for StrongSwan + raise falcon.HTTPForbidden("Forbidden", "Invalid serial number supplied") + + xattr.setxattr(path, "user.lease.address", req.get_param("address", required=True).encode("ascii")) + xattr.setxattr(path, "user.lease.last_seen", datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z") + push.publish("lease-update", common_name) # client-disconnect is pretty much unusable: # - Android Connect Client results "IP packet with unknown IP version=2" on gateway diff --git a/certidude/cli.py b/certidude/cli.py index 66d0d0d..c4d5812 100755 --- a/certidude/cli.py +++ b/certidude/cli.py @@ -15,8 +15,7 @@ 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 -from certidude.decorators import apt, rpm, pip +from certidude.common import expand_paths, ip_address, ip_network, apt, rpm, pip from cryptography import x509 from cryptography.x509.oid import NameOID, ExtendedKeyUsageOID from cryptography.hazmat.backends import default_backend @@ -39,28 +38,6 @@ env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=Tr NOW = datetime.utcnow().replace(tzinfo=None) -CERTIDUDE_TIMER = """ -[Unit] -Description=Run certidude service weekly - -[Timer] -OnCalendar=weekly -Persistent=true -Unit=certidude.service - -[Install] -WantedBy=timers.target -""" - -CERTIDUDE_SERVICE = """ -[Unit] -Description=Renew certificates and update revocation lists - -[Service] -Type=simple -ExecStart=%s request -""" - @click.command("request", help="Run processes for requesting certificates and configuring services") @click.option("-r", "--renew", default=False, is_flag=True, help="Renew now") @click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background") @@ -88,15 +65,17 @@ def certidude_request(fork, renew): if not os.path.exists(run_dir): click.echo("Creating: %s" % run_dir) os.makedirs(run_dir) + context = globals() + context.update(locals()) if not os.path.exists("/etc/systemd/system/certidude.timer"): click.echo("Creating systemd timer...") with open("/etc/systemd/system/certidude.timer", "w") as fh: - fh.write(CERTIDUDE_TIMER) + fh.write(env.get_template("client/certidude.timer").render(context)) if not os.path.exists("/etc/systemd/system/certidude.service"): click.echo("Creating systemd service...") with open("/etc/systemd/system/certidude.service", "w") as fh: - fh.write(CERTIDUDE_SERVICE % sys.argv[0]) + fh.write(env.get_template("client/certidude.service").render(context)) for authority in clients.sections(): @@ -364,6 +343,9 @@ def certidude_request(fork, renew): type=click.File(mode="w", atomic=True, lazy=True), help="OpenVPN configuration file") def certidude_setup_openvpn_server(authority, config, subnet, route, local, proto, port): + # Install dependencies + apt("openvpn") + rpm("openvpn") # Create corresponding section in Certidude client configuration file client_config = ConfigParser() @@ -506,9 +488,10 @@ def certidude_setup_nginx(authority, site_config, tls_config, common_name, direc default="/etc/openvpn/client-to-site.conf", type=click.File(mode="w", atomic=True, lazy=True), help="OpenVPN configuration file") -@apt("openvpn python-requests-kerberos") -@rpm("openvpn python2-requests-kerberos") def certidude_setup_openvpn_client(authority, remote, config, proto): + # Install dependencies + apt("openvpn") + rpm("openvpn") # Create corresponding section in Certidude client configuration file client_config = ConfigParser() @@ -531,7 +514,7 @@ def certidude_setup_openvpn_client(authority, remote, config, proto): click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH)) # Create corresponding section in /etc/certidude/services.conf - endpoint = "OpenVPN connection to %s" % remote + endpoint = "OpenVPN to %s" % remote service_config = ConfigParser() if os.path.exists(const.SERVICES_CONFIG_PATH): service_config.readfp(open(const.SERVICES_CONFIG_PATH)) @@ -574,13 +557,15 @@ def certidude_setup_openvpn_client(authority, remote, config, proto): @click.option("--common-name", "-cn", default=const.FQDN, help="Common name, %s by default" % const.FQDN) @click.option("--subnet", "-sn", default=u"192.168.33.0/24", type=ip_network, help="IPsec virtual subnet, 192.168.33.0/24 by default") @click.option("--route", "-r", type=ip_network, multiple=True, help="Subnets to advertise via this connection, multiple allowed") -@apt("strongswan python-requests python-requests-kerberos") -@rpm("strongswan python2-requests python2-requests-kerberos") -@pip("ipsecparse") def certidude_setup_strongswan_server(authority, common_name, subnet, route): if "." not in common_name: raise ValueError("Hostname has to be fully qualified!") + # Install dependencies + apt("strongswan") + rpm("strongswan") + pip("ipsecparse") + # Create corresponding section in Certidude client configuration file client_config = ConfigParser() if os.path.exists(const.CLIENT_CONFIG_PATH): @@ -605,14 +590,10 @@ def certidude_setup_strongswan_server(authority, common_name, subnet, route): from ipsecparse import loads config = loads(open("%s/ipsec.conf" % const.STRONGSWAN_PREFIX).read()) config["conn", authority] = dict( - leftsourceip="%config", - left=common_name, leftcert=client_config.get(authority, "certificate path"), leftsubnet=",".join(route), right="%any", rightsourceip=str(subnet), - keyexchange="ikev2", - keyingtries="300", closeaction="restart", auto="ignore") with open("%s/ipsec.conf.part" % const.STRONGSWAN_PREFIX, "w") as fh: @@ -629,10 +610,12 @@ def certidude_setup_strongswan_server(authority, common_name, subnet, route): @click.command("client", help="Set up strongSwan client") @click.argument("authority") @click.argument("remote") -@apt("strongswan python-requests python-requests-kerberos") -@rpm("strongswan python2-requests python2-requests-kerberos") -@pip("ipsecparse") def certidude_setup_strongswan_client(authority, remote): + # Install dependencies + apt("strongswan") + rpm("strongswan") + pip("ipsecparse") + # Create corresponding section in /etc/certidude/client.conf client_config = ConfigParser() if os.path.exists(const.CLIENT_CONFIG_PATH): @@ -698,9 +681,11 @@ def certidude_setup_strongswan_client(authority, remote): @click.command("networkmanager", help="Set up strongSwan client via NetworkManager") @click.argument("authority") # Certidude server @click.argument("remote") # StrongSwan gateway -@apt("strongswan-nm python-requests python-requests-kerberos") -@rpm("NetworkManager-strongswan-gnome python2-requests python2-requests-kerberos") def certidude_setup_strongswan_networkmanager(authority, remote): + # Install dependencies + apt("strongswan-nm") + rpm("NetworkManager-strongswan-gnome") + endpoint = "IPSec to %s" % remote # Create corresponding section in /etc/certidude/client.conf @@ -799,11 +784,13 @@ def certidude_setup_openvpn_networkmanager(authority, remote): @click.option("--directory", help="Directory for authority files") @click.option("--server-flags", is_flag=True, help="Add TLS Server and IKE Intermediate extended key usage flags") @click.option("--outbox", default="smtp://smtp.%s" % const.DOMAIN, help="SMTP server, smtp://smtp.%s by default" % const.DOMAIN) -@apt("python-setproctitle python-openssl python-falcon python-humanize python-markdown python-xattr") -@rpm("python-setproctitle pyOpenSSL python-falcon python-humanize python-markdown pyxattr") def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, state, locality, organization, organizational_unit, common_name, directory, authority_lifetime, push_server, outbox, server_flags): - openvpn_profile_template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates", "openvpn-client.conf") - bootstrap_template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates", "bootstrap.conf") + # Install dependencies + apt("python-setproctitle python-openssl python-falcon python-humanize python-markdown python-xattr") + rpm("python-setproctitle pyOpenSSL python-falcon python-humanize python-markdown pyxattr") + pip("gssapi") + + template_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates") if not directory: if os.getuid(): @@ -848,7 +835,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, base = ",".join(["dc=" + j for j in domain.split(".")]) if not os.path.exists("/etc/cron.hourly/certidude"): with open("/etc/cron.hourly/certidude", "w") as fh: - fh.write(env.get_template("ldap-ticket-renewal.sh").render(vars())) + fh.write(env.get_template("server/cronjob").render(vars())) os.chmod("/etc/cron.hourly/certidude", 0o755) click.echo("Created /etc/cron.hourly/certidude for automatic LDAP service ticket renewal, inspect and adjust accordingly") os.system("/etc/cron.hourly/certidude") @@ -865,7 +852,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, port = "80" else: port = "8080" - nginx_config.write(env.get_template("nginx.conf").render(vars())) + nginx_config.write(env.get_template("server/nginx.conf").render(vars())) click.echo("Generated: %s" % nginx_config.name) if not os.path.exists("/etc/nginx/sites-enabled/certidude.conf"): os.symlink("../sites-available/certidude.conf", "/etc/nginx/sites-enabled/certidude.conf") @@ -880,7 +867,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, click.echo("File /etc/systemd/system/certidude.service already exists, remove to regenerate") else: with open("/etc/systemd/system/certidude.service", "w") as fh: - fh.write(env.get_template("systemd.service").render(vars())) + fh.write(env.get_template("server/systemd.service").render(vars())) click.echo("File /etc/systemd/system/certidude.service created") else: NotImplemented # No systemd @@ -900,7 +887,7 @@ def certidude_setup_authority(username, kerberos_keytab, nginx_config, country, os.umask(0o137) push_token = "".join([random.choice(string.ascii_letters + string.digits) for j in range(0,32)]) with open(const.CONFIG_PATH, "w") as fh: - fh.write(env.get_template("certidude-server.conf").render(vars())) + fh.write(env.get_template("server/server.conf").render(vars())) click.echo("Generated %s" % const.CONFIG_PATH) if os.path.lexists(directory): diff --git a/certidude/common.py b/certidude/common.py index a069cd6..666200a 100644 --- a/certidude/common.py +++ b/certidude/common.py @@ -35,3 +35,29 @@ def expand_paths(): return wrapped return wrapper + +def apt(packages): + """ + Install packages for Debian and Ubuntu + """ + if os.path.exists("/usr/bin/apt-get"): + cmd = ["/usr/bin/apt-get", "install", "-yqq"] + packages.split(" ") + click.echo("Running: %s" % " ".join(cmd)) + subprocess.call(cmd) + + +def rpm(packages): + """ + Install packages for Fedora and CentOS + """ + if os.path.exists("/usr/bin/dnf"): + cmd = ["/usr/bin/dnf", "install", "-y"] + packages.split(" ") + click.echo("Running: %s" % " ".join(cmd)) + subprocess.call(cmd) + + +def pip(packages): + click.echo("Running: pip install %s" % packages) + import pip + pip.main(['install'] + packages.split(" ")) + diff --git a/certidude/config.py b/certidude/config.py index 0473376..7f7e317 100644 --- a/certidude/config.py +++ b/certidude/config.py @@ -74,6 +74,11 @@ EVENT_SOURCE_SUBSCRIBE = cp.get("push", "event source subscribe") LONG_POLL_PUBLISH = cp.get("push", "long poll publish") LONG_POLL_SUBSCRIBE = cp.get("push", "long poll subscribe") +if os.getenv("TRAVIS"): # TODO: include nginx setup in Travis + EVENT_SOURCE_PUBLISH = "" + LONG_POLL_PUBLISH = "" + LONG_POLL_SUBSCRIBE = "" + LOGGING_BACKEND = cp.get("logging", "backend") if "whitelist" == AUTHORIZATION_BACKEND: diff --git a/certidude/decorators.py b/certidude/decorators.py index 49bebcd..b9a44c6 100644 --- a/certidude/decorators.py +++ b/certidude/decorators.py @@ -88,43 +88,3 @@ def serialize(func): resp.body = json.dumps(func(instance, req, resp, **kwargs), cls=MyEncoder) return wrapped - -def apt(packages): - """ - Install packages for Debian and Ubuntu - """ - def wrapper(func): - def wrapped(*args, **kwargs): - if os.path.exists("/usr/bin/apt-get"): - cmd = ["/usr/bin/apt-get", "install", "-yqq"] + packages.split(" ") - click.echo("Running: %s" % " ".join(cmd)) - subprocess.call(cmd) - return func(*args, **kwargs) - return wrapped - return wrapper - - -def rpm(packages): - """ - Install packages for Fedora and CentOS - """ - def wrapper(func): - def wrapped(*args, **kwargs): - if os.path.exists("/usr/bin/dnf"): - cmd = ["/usr/bin/dnf", "install", "-y"] + packages.split(" ") - click.echo("Running: %s" % " ".join(cmd)) - subprocess.call(cmd) - return func(*args, **kwargs) - return wrapped - return wrapper - - -def pip(packages): - def wrapper(func): - def wrapped(*args, **kwargs): - click.echo("Running: pip install %s" % packages) - import pip - pip.main(['install'] + packages.split(" ")) - return func(*args, **kwargs) - return wrapped - return wrapper diff --git a/certidude/templates/client/certidude.service b/certidude/templates/client/certidude.service new file mode 100644 index 0000000..86b5ff5 --- /dev/null +++ b/certidude/templates/client/certidude.service @@ -0,0 +1,6 @@ +[Unit] +Description=Renew certificates and update revocation lists + +[Service] +Type=simple +ExecStart={{ sys.argv[0] }} request diff --git a/certidude/templates/client/certidude.timer b/certidude/templates/client/certidude.timer new file mode 100644 index 0000000..f53831a --- /dev/null +++ b/certidude/templates/client/certidude.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Run certidude service weekly + +[Timer] +OnCalendar=weekly +Persistent=true +Unit=certidude.service + +[Install] +WantedBy=timers.target + diff --git a/certidude/templates/ldap-ticket-renewal.sh b/certidude/templates/server/cronjob similarity index 100% rename from certidude/templates/ldap-ticket-renewal.sh rename to certidude/templates/server/cronjob diff --git a/certidude/templates/nginx.conf b/certidude/templates/server/nginx.conf similarity index 100% rename from certidude/templates/nginx.conf rename to certidude/templates/server/nginx.conf diff --git a/certidude/templates/certidude-server.conf b/certidude/templates/server/server.conf similarity index 97% rename from certidude/templates/certidude-server.conf rename to certidude/templates/server/server.conf index a443666..65a67ce 100644 --- a/certidude/templates/certidude-server.conf +++ b/certidude/templates/server/server.conf @@ -142,7 +142,7 @@ format = p12 # Template for OpenVPN profile, copy certidude/templates/openvpn-client.conf # to /etc/certidude/ and make modifications as necessary -openvpn profile template = {{ openvpn_profile_template_path }} +openvpn profile template = {{ template_path }}/openvpn-client.conf [tagging] owner/string = Owner @@ -154,4 +154,4 @@ other/ = Other # Following can be used to set up clients easily: certidude bootstrap ca.example.lan # Services template is rendered on certidude server with relevant variables and # placed to /etc/certidude/services.conf on the client -services template = {{ bootstrap_template_path }} +services template = {{ template_path }}/bootstrap.conf diff --git a/certidude/templates/systemd.service b/certidude/templates/server/systemd.service similarity index 100% rename from certidude/templates/systemd.service rename to certidude/templates/server/systemd.service