certidude/certidude/cli.py

1089 lines
46 KiB
Python
Raw Normal View History

2015-08-13 08:11:08 +00:00
#!/usr/bin/env python3
2015-07-12 19:22:10 +00:00
# coding: utf-8
2015-07-26 20:34:46 +00:00
import asyncore
2015-08-13 08:11:08 +00:00
import click
import configparser
import hashlib
2015-08-13 08:11:08 +00:00
import logging
2015-07-12 19:22:10 +00:00
import os
2015-08-13 08:11:08 +00:00
import pwd
2015-07-12 19:22:10 +00:00
import re
import requests
2015-07-26 20:34:46 +00:00
import signal
2015-08-13 08:11:08 +00:00
import socket
2015-07-27 15:49:50 +00:00
import subprocess
2015-08-13 08:11:08 +00:00
import sys
from certidude.signer import SignServer
from certidude.common import expand_paths
2015-08-13 08:11:08 +00:00
from datetime import datetime
2015-07-26 20:34:46 +00:00
from humanize import naturaltime
2015-10-16 15:44:42 +00:00
from ipaddress import ip_network, ip_address
2015-08-13 08:11:08 +00:00
from jinja2 import Environment, PackageLoader
2015-07-26 20:34:46 +00:00
from time import sleep
from setproctitle import setproctitle
2015-08-13 08:11:08 +00:00
from OpenSSL import crypto
2016-02-28 20:37:56 +00:00
from future.standard_library import install_aliases
install_aliases()
2015-08-13 08:11:08 +00:00
env = Environment(loader=PackageLoader("certidude", "templates"), trim_blocks=True)
2015-07-12 19:22:10 +00:00
# Big fat warning:
# m2crypto overflows around 2030 because on 32-bit systems
# m2crypto does not support hardware engine support (?)
# m2crypto CRL object is pretty much useless
2015-07-26 20:34:46 +00:00
2015-07-12 19:22:10 +00:00
# pyopenssl has no straight-forward methods for getting RSA key modulus
2015-07-26 20:34:46 +00:00
# pyopenssl 0.13 bundled with Ubuntu 14.04 has no get_extension_count() for X509Req objects
assert hasattr(crypto.X509Req(), "get_extensions"), "You're running too old version of pyopenssl, upgrade to 0.15+"
2015-07-12 19:22:10 +00:00
# http://www.mad-hacking.net/documentation/linux/security/ssl-tls/creating-ca.xml
2015-07-26 20:34:46 +00:00
# https://kjur.github.io/jsrsasign/
# keyUsage, extendedKeyUsage - https://www.openssl.org/docs/apps/x509v3_config.html
2015-08-13 08:11:08 +00:00
# strongSwan key paths - https://wiki.strongswan.org/projects/1/wiki/SimpleCA
2015-07-12 19:22:10 +00:00
2015-07-26 20:34:46 +00:00
# Parse command-line argument defaults from environment
HOSTNAME = socket.gethostname()
2016-02-28 20:37:56 +00:00
FQDN = socket.getaddrinfo(HOSTNAME, 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3]
2015-07-26 20:34:46 +00:00
USERNAME = os.environ.get("USER")
2015-07-12 19:22:10 +00:00
NOW = datetime.utcnow().replace(tzinfo=None)
2015-07-26 20:34:46 +00:00
FIRST_NAME = None
SURNAME = None
2015-08-13 08:11:08 +00:00
EMAIL = None
if USERNAME:
EMAIL = USERNAME + "@" + FQDN
2015-07-26 20:34:46 +00:00
if os.getuid() >= 1000:
_, _, _, _, gecos, _, _ = pwd.getpwnam(USERNAME)
if " " in gecos:
FIRST_NAME, SURNAME = gecos.split(" ", 1)
else:
FIRST_NAME = gecos
2016-01-15 09:18:27 +00:00
@click.command("spawn", help="Run processes for requesting certificates and configuring services")
@click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background")
2016-01-15 09:18:27 +00:00
def certidude_request_spawn(fork):
from certidude.helpers import certidude_request_certificate
2016-02-28 20:37:56 +00:00
clients = configparser.ConfigParser()
clients.readfp(open("/etc/certidude/client.conf"))
services = ConfigParser()
services.readfp(open("/etc/certidude/services.conf"))
# Process directories
run_dir = "/run/certidude"
# Prepare signer PID-s directory
if not os.path.exists(run_dir):
click.echo("Creating: %s" % run_dir)
os.makedirs(run_dir)
for server in clients.sections():
if clients.get(server, "managed") != "true":
continue
pid_path = os.path.join(run_dir, server + ".pid")
try:
with open(pid_path) as fh:
pid = int(fh.readline())
os.kill(pid, signal.SIGTERM)
click.echo("Terminated process %d" % pid)
os.unlink(pid_path)
except (ValueError, ProcessLookupError, FileNotFoundError):
pass
if fork:
child_pid = os.fork()
else:
child_pid = None
if child_pid:
click.echo("Spawned certificate request process with PID %d" % (child_pid))
continue
with open(pid_path, "w") as fh:
fh.write("%d\n" % os.getpid())
setproctitle("certidude request spawn %s" % server)
retries = 30
while retries > 0:
try:
certidude_request_certificate(
2016-01-15 11:50:45 +00:00
server,
clients.get(server, "key_path"),
clients.get(server, "request_path"),
clients.get(server, "certificate_path"),
clients.get(server, "authority_path"),
socket.gethostname(),
None,
autosign=True,
wait=True)
break
except requests.exceptions.Timeout:
retries -= 1
continue
for endpoint in services.sections():
if services.get(endpoint, "authority") != server:
continue
csummer = hashlib.sha1()
csummer.update(endpoint.encode("ascii"))
csum = csummer.hexdigest()
uuid = csum[:8] + "-" + csum[8:12] + "-" + csum[12:16] + "-" + csum[16:20] + "-" + csum[20:32]
# Set up IPsec via NetworkManager
if services.get(endpoint, "service") == "network-manager/strongswan":
config = configparser.ConfigParser()
config.add_section("connection")
config.add_section("vpn")
config.add_section("ipv4")
config.set("connection", "id", endpoint)
config.set("connection", "uuid", uuid)
config.set("connection", "type", "vpn")
config.set("vpn", "service-type", "org.freedesktop.NetworkManager.strongswan")
config.set("vpn", "userkey", clients.get(server, "key_path"))
config.set("vpn", "usercert", clients.get(server, "certificate_path"))
config.set("vpn", "encap", "no")
config.set("vpn", "address", services.get(endpoint, "remote"))
config.set("vpn", "virtual", "yes")
config.set("vpn", "method", "key")
config.set("vpn", "certificate", clients.get(server, "authority_path"))
config.set("vpn", "ipcomp", "no")
config.set("ipv4", "method", "auto")
# Add routes, may need some more tweaking
if services.has_option(endpoint, "route"):
for index, subnet in enumerate(services.get(endpoint, "route").split(","), start=1):
config.set("ipv4", "route%d" % index, subnet)
# Prevent creation of files with liberal permissions
os.umask(0o177)
# Write keyfile
with open(os.path.join("/etc/NetworkManager/system-connections", endpoint), "w") as configfile:
config.write(configfile)
continue
# Set up IPsec via /etc/ipsec.conf
if services.get(endpoint, "service") == "strongswan":
from ipsecparse import loads
config = loads(open('/etc/ipsec.conf').read())
config["conn", endpoint] = dict(
leftsourceip="%config",
left="%defaultroute",
leftcert=clients.get(server, "certificate_path"),
rightid="%any",
right=services.get(endpoint, "remote"),
rightsubnet=services.get(endpoint, "route"),
keyexchange="ikev2",
keyingtries="300",
dpdaction="restart",
closeaction="restart",
auto="start")
with open("/etc/ipsec.conf.part", "w") as fh:
fh.write(config.dumps())
os.rename("/etc/ipsec.conf.part", "/etc/ipsec.conf")
# Regenerate /etc/ipsec.secrets
with open("/etc/ipsec.secrets.part", "w") as fh:
for filename in os.listdir("/etc/ipsec.d/private"):
if not filename.endswith(".pem"):
continue
fh.write(": RSA /etc/ipsec.d/private/%s\n" % filename)
os.rename("/etc/ipsec.secrets.part", "/etc/ipsec.secrets")
# Attempt to reload config or start if it's not running
if os.system("ipsec update") == 130:
os.system("ipsec start")
continue
# TODO: OpenVPN, Puppet, OpenLDAP, intranet HTTPS, <insert awesomeness here>
os.unlink(pid_path)
2016-01-15 09:18:27 +00:00
@click.command("spawn", help="Run privilege isolated signer process")
@click.option("-k", "--kill", default=False, is_flag=True, help="Kill previous instance")
2015-08-13 08:11:08 +00:00
@click.option("-n", "--no-interaction", default=True, is_flag=True, help="Don't load password protected keys")
2016-01-15 09:18:27 +00:00
def certidude_signer_spawn(kill, no_interaction):
2015-07-26 20:34:46 +00:00
"""
Spawn privilege isolated signer process
2015-07-26 20:34:46 +00:00
"""
from certidude import config
2016-01-10 17:51:54 +00:00
_, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude")
os.setgid(gid)
# Check whether we have privileges
os.umask(0o027)
uid = os.getuid()
if uid != 0:
raise click.ClickException("Not running as root")
2015-07-26 20:34:46 +00:00
# Process directories
run_dir = "/run/certidude"
# Prepare signer PID-s directory
if not os.path.exists(run_dir):
click.echo("Creating: %s" % run_dir)
os.makedirs(run_dir)
2015-07-26 20:34:46 +00:00
2015-08-13 08:11:08 +00:00
# Preload charmap encoding for byte_string() function of pyOpenSSL
# in order to enable chrooting
"".encode("charmap")
2015-07-26 20:34:46 +00:00
# Prepare chroot directories
chroot_dir = os.path.join(run_dir, "jail")
2015-07-26 20:34:46 +00:00
if not os.path.exists(os.path.join(chroot_dir, "dev")):
os.makedirs(os.path.join(chroot_dir, "dev"))
if not os.path.exists(os.path.join(chroot_dir, "dev", "urandom")):
# TODO: use os.mknod instead
os.system("mknod -m 444 %s c 1 9" % os.path.join(chroot_dir, "dev", "urandom"))
try:
with open(config.SIGNER_PID_PATH) as fh:
pid = int(fh.readline())
os.kill(pid, 0)
click.echo("Found process with PID %d" % pid)
except (ValueError, ProcessLookupError, FileNotFoundError):
pid = 0
if pid > 0:
if kill:
try:
click.echo("Killing %d" % pid)
os.kill(pid, signal.SIGTERM)
sleep(1)
os.kill(pid, signal.SIGKILL)
sleep(1)
except ProcessLookupError:
pass
child_pid = os.fork()
if child_pid:
click.echo("Spawned certidude signer process with PID %d at %s" % (child_pid, config.SIGNER_SOCKET_PATH))
return
2016-01-15 09:18:27 +00:00
setproctitle("certidude signer spawn")
with open(config.SIGNER_PID_PATH, "w") as fh:
fh.write("%d\n" % os.getpid())
logging.basicConfig(
filename="/var/log/signer.log",
level=logging.INFO)
server = SignServer(
config.SIGNER_SOCKET_PATH,
config.AUTHORITY_PRIVATE_KEY_PATH,
config.AUTHORITY_CERTIFICATE_PATH,
config.CERTIFICATE_LIFETIME,
config.CERTIFICATE_BASIC_CONSTRAINTS,
config.CERTIFICATE_KEY_USAGE_FLAGS,
config.CERTIFICATE_EXTENDED_KEY_USAGE_FLAGS,
config.REVOCATION_LIST_LIFETIME)
asyncore.loop()
2015-07-26 20:34:46 +00:00
@click.command("client", help="Setup X.509 certificates for application")
@click.argument("url") #, help="Certidude authority endpoint URL")
@click.option("--common-name", "-cn", default=HOSTNAME, help="Common name, '%s' by default" % HOSTNAME)
@click.option("--org-unit", "-ou", help="Organizational unit")
@click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, '%s' by default" % EMAIL)
@click.option("--given-name", "-gn", default=FIRST_NAME, help="Given name of the person associted with the certificate, '%s' by default" % FIRST_NAME)
@click.option("--surname", "-sn", default=SURNAME, help="Surname of the person associted with the certificate, '%s' by default" % SURNAME)
@click.option("--key-usage", "-ku", help="Key usage attributes, none requested by default")
@click.option("--extended-key-usage", "-eku", help="Extended key usage attributes, none requested by default")
@click.option("--quiet", "-q", default=False, is_flag=True, help="Disable verbose output")
@click.option("--autosign", "-s", default=False, is_flag=True, help="Request for automatic signing if available")
@click.option("--wait", "-w", default=False, is_flag=True, help="Wait for certificate, by default return immideately")
@click.option("--key-path", "-k", default=HOSTNAME + ".key", help="Key path, %s.key by default" % HOSTNAME)
@click.option("--request-path", "-r", default=HOSTNAME + ".csr", help="Request path, %s.csr by default" % HOSTNAME)
@click.option("--certificate-path", "-c", default=HOSTNAME + ".crt", help="Certificate path, %s.crt by default" % HOSTNAME)
@click.option("--authority-path", "-a", default="ca.crt", help="Certificate authority certificate path, ca.crt by default")
def certidude_setup_client(quiet, **kwargs):
from certidude.helpers import certidude_request_certificate
2015-07-26 20:34:46 +00:00
return certidude_request_certificate(**kwargs)
@click.command("server", help="Set up OpenVPN server")
@click.argument("url")
@click.option("--common-name", "-cn", default=FQDN, help="Common name, %s by default" % FQDN)
2015-07-26 20:34:46 +00:00
@click.option("--org-unit", "-ou", help="Organizational unit")
@click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, '%s' by default" % EMAIL)
@click.option("--subnet", "-s", default="192.168.33.0/24", type=ip_network, help="OpenVPN subnet, 192.168.33.0/24 by default")
2015-09-09 08:19:17 +00:00
@click.option("--local", "-l", default="127.0.0.1", help="OpenVPN listening address, defaults to 127.0.0.1")
2015-07-26 20:34:46 +00:00
@click.option("--port", "-p", default=1194, type=click.IntRange(1,60000), help="OpenVPN listening port, 1194 by default")
@click.option('--proto', "-t", default="udp", type=click.Choice(['udp', 'tcp']), help="OpenVPN transport protocol, UDP by default")
2015-08-13 08:11:08 +00:00
@click.option("--route", "-r", type=ip_network, multiple=True, help="Subnets to advertise via this connection, multiple allowed")
2015-07-26 20:34:46 +00:00
@click.option("--config", "-o",
default="/etc/openvpn/site-to-client.conf",
type=click.File(mode="w", atomic=True, lazy=True),
help="OpenVPN configuration file")
@click.option("--directory", "-d", default="/etc/openvpn/keys", help="Directory for keys, /etc/openvpn/keys by default")
2015-07-27 15:49:50 +00:00
@click.option("--key-path", "-key", default=HOSTNAME + ".key", help="Key path, %s.key relative to --directory by default" % HOSTNAME)
@click.option("--request-path", "-csr", default=HOSTNAME + ".csr", help="Request path, %s.csr relative to --directory by default" % HOSTNAME)
@click.option("--certificate-path", "-crt", default=HOSTNAME + ".crt", help="Certificate path, %s.crt relative to --directory by default" % HOSTNAME)
@click.option("--dhparam-path", "-dh", default="dhparam2048.pem", help="Diffie/Hellman parameters path, dhparam2048.pem relative to --directory by default")
@click.option("--authority-path", "-ca", default="ca.crt", help="Certificate authority certificate path, ca.crt relative to --dir by default")
2015-08-13 08:11:08 +00:00
@expand_paths()
def certidude_setup_openvpn_server(url, config, subnet, route, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, dhparam_path, local, proto, port):
2015-07-26 20:34:46 +00:00
# TODO: Intelligent way of getting last IP address in the subnet
from certidude.helpers import certidude_request_certificate
2015-07-26 20:34:46 +00:00
subnet_first = None
subnet_last = None
subnet_second = None
for addr in subnet.hosts():
if not subnet_first:
subnet_first = addr
continue
if not subnet_second:
subnet_second = addr
subnet_last = addr
if not os.path.exists(certificate_path):
click.echo("As OpenVPN server certificate needs specific key usage extensions please")
click.echo("use following command to sign on Certidude server instead of web interface:")
click.echo()
click.echo(" certidude sign %s" % common_name)
retval = certidude_request_certificate(
url,
key_path,
request_path,
certificate_path,
authority_path,
common_name,
org_unit,
email_address,
key_usage="nonRepudiation,digitalSignature,keyEncipherment",
2015-08-13 08:11:08 +00:00
extended_key_usage="serverAuth,ikeIntermediate",
2015-07-26 20:34:46 +00:00
wait=True)
2015-07-27 15:49:50 +00:00
if not os.path.exists(dhparam_path):
cmd = "openssl", "dhparam", "-out", dhparam_path, "2048"
subprocess.check_call(cmd)
2015-07-26 20:34:46 +00:00
if retval:
return retval
# TODO: Add dhparam
2015-08-13 08:11:08 +00:00
config.write(env.get_template("openvpn-site-to-client.ovpn").render(locals()))
2015-07-26 20:34:46 +00:00
click.echo("Generated %s" % config.name)
click.echo()
click.echo("Inspect newly created %s and start OpenVPN service:" % config.name)
click.echo()
click.secho(" service openvpn restart", bold=True)
click.echo()
@click.command("client", help="Set up OpenVPN client")
@click.argument("url")
@click.argument("remote")
@click.option('--proto', "-t", default="udp", type=click.Choice(['udp', 'tcp']), help="OpenVPN transport protocol, UDP by default")
@click.option("--common-name", "-cn", default=HOSTNAME, help="Common name, %s by default" % HOSTNAME)
@click.option("--org-unit", "-ou", help="Organizational unit")
@click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, '%s' by default" % EMAIL)
@click.option("--config", "-o",
default="/etc/openvpn/client-to-site.conf",
type=click.File(mode="w", atomic=True, lazy=True),
help="OpenVPN configuration file")
@click.option("--directory", "-d", default="/etc/openvpn/keys", help="Directory for keys, /etc/openvpn/keys by default")
@click.option("--key-path", "-k", default=HOSTNAME + ".key", help="Key path, %s.key relative to --directory by default" % HOSTNAME)
@click.option("--request-path", "-r", default=HOSTNAME + ".csr", help="Request path, %s.csr relative to --directory by default" % HOSTNAME)
@click.option("--certificate-path", "-c", default=HOSTNAME + ".crt", help="Certificate path, %s.crt relative to --directory by default" % HOSTNAME)
@click.option("--authority-path", "-a", default="ca.crt", help="Certificate authority certificate path, ca.crt relative to --dir by default")
2015-08-13 08:11:08 +00:00
@expand_paths()
2015-07-26 20:34:46 +00:00
def certidude_setup_openvpn_client(url, config, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, proto, remote):
from certidude.helpers import certidude_request_certificate
2015-07-26 20:34:46 +00:00
retval = certidude_request_certificate(
url,
key_path,
request_path,
certificate_path,
authority_path,
common_name,
org_unit,
email_address,
wait=True)
if retval:
return retval
2015-07-12 19:22:10 +00:00
2015-07-26 20:34:46 +00:00
# TODO: Add dhparam
2015-08-13 08:11:08 +00:00
config.write(env.get_template("openvpn-client-to-site.ovpn").render(locals()))
2015-07-26 20:34:46 +00:00
click.echo("Generated %s" % config.name)
click.echo()
click.echo("Inspect newly created %s and start OpenVPN service:" % config.name)
click.echo()
click.echo(" service openvpn restart")
click.echo()
2015-08-13 08:11:08 +00:00
@click.command("server", help="Set up strongSwan server")
@click.argument("url")
@click.option("--common-name", "-cn", default=FQDN, help="Common name, %s by default" % FQDN)
2015-08-13 08:11:08 +00:00
@click.option("--org-unit", "-ou", help="Organizational unit")
@click.option("--fqdn", "-f", default=FQDN, help="Fully qualified hostname associated with the certificate")
2015-08-13 08:11:08 +00:00
@click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, %s by default" % EMAIL)
@click.option("--subnet", "-s", default="192.168.33.0/24", type=ip_network, help="IPsec virtual subnet, 192.168.33.0/24 by default")
@click.option("--local", "-l", default=None, type=ip_address, help="IP address associated with the certificate, none by default")
2015-08-13 08:11:08 +00:00
@click.option("--route", "-r", type=ip_network, multiple=True, help="Subnets to advertise via this connection, multiple allowed")
@click.option("--config", "-o",
default="/etc/ipsec.conf",
type=click.File(mode="w", atomic=True, lazy=True),
help="strongSwan configuration file, /etc/ipsec.conf by default")
@click.option("--secrets", "-s",
default="/etc/ipsec.secrets",
type=click.File(mode="w", atomic=True, lazy=True),
help="strongSwan secrets file, /etc/ipsec.secrets by default")
@click.option("--directory", "-d", default="/etc/ipsec.d", help="Directory for keys, /etc/ipsec.d by default")
@click.option("--key-path", "-key", default="private/%s.pem" % HOSTNAME, help="Key path, private/%s.pem by default" % HOSTNAME)
@click.option("--request-path", "-csr", default="reqs/%s.pem" % HOSTNAME, help="Request path, reqs/%s.pem by default" % HOSTNAME)
@click.option("--certificate-path", "-crt", default="certs/%s.pem" % HOSTNAME, help="Certificate path, certs/%s.pem by default" % HOSTNAME)
@click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default")
@expand_paths()
2015-10-16 15:44:42 +00:00
def certidude_setup_strongswan_server(url, config, secrets, subnet, route, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, local, fqdn):
if "." not in common_name:
raise ValueError("Hostname has to be fully qualified!")
if not local:
raise ValueError("Please specify local IP address")
2015-08-13 08:11:08 +00:00
if not os.path.exists(certificate_path):
click.echo("As strongSwan server certificate needs specific key usage extensions please")
click.echo("use following command to sign on Certidude server instead of web interface:")
click.echo()
click.echo(" certidude sign %s" % common_name)
from certidude.helpers import certidude_request_certificate
2015-08-13 08:11:08 +00:00
retval = certidude_request_certificate(
url,
key_path,
request_path,
certificate_path,
authority_path,
common_name,
org_unit,
email_address,
key_usage="nonRepudiation,digitalSignature,keyEncipherment",
extended_key_usage="serverAuth,1.3.6.1.5.5.8.2.2",
ip_address=local,
dns=fqdn,
2015-08-13 08:11:08 +00:00
wait=True)
if retval:
return retval
config.write(env.get_template("strongswan-site-to-client.conf").render(locals()))
secrets.write(": RSA %s\n" % key_path)
2015-08-13 08:11:08 +00:00
click.echo("Generated %s and %s" % (config.name, secrets.name))
2015-08-13 08:11:08 +00:00
click.echo()
click.echo("Inspect newly created %s and start strongSwan service:" % config.name)
click.echo()
click.echo(" apt-get install strongswan strongswan-starter strongswan-ikev2")
click.secho(" service strongswan restart", bold=True)
click.echo()
@click.command("client", help="Set up strongSwan client")
@click.argument("url")
@click.argument("remote")
@click.option("--common-name", "-cn", default=HOSTNAME, help="Common name, %s by default" % HOSTNAME)
@click.option("--org-unit", "-ou", help="Organizational unit")
@click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, '%s' by default" % EMAIL)
@click.option("--config", "-o",
default="/etc/ipsec.conf",
type=click.File(mode="w", atomic=True, lazy=True),
help="strongSwan configuration file, /etc/ipsec.conf by default")
@click.option("--secrets", "-s",
default="/etc/ipsec.secrets",
type=click.File(mode="w", atomic=True, lazy=True),
help="strongSwan secrets file, /etc/ipsec.secrets by default")
@click.option("--dpdaction", "-d",
default="restart",
type=click.Choice(["none", "clear", "hold", "restart"]),
help="Action upon dead peer detection; either none, clear, hold or restart")
@click.option("--auto", "-a",
default="start",
type=click.Choice(["ignore", "add", "route", "start"]),
help="Operation at startup; either ignore, add, route or start")
@click.option("--directory", "-d", default="/etc/ipsec.d", help="Directory for keys, /etc/ipsec.d by default")
@click.option("--key-path", "-key", default="private/%s.pem" % HOSTNAME, help="Key path, private/%s.pem by default" % HOSTNAME)
@click.option("--request-path", "-csr", default="reqs/%s.pem" % HOSTNAME, help="Request path, reqs/%s.pem by default" % HOSTNAME)
@click.option("--certificate-path", "-crt", default="certs/%s.pem" % HOSTNAME, help="Certificate path, certs/%s.pem by default" % HOSTNAME)
@click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default")
@expand_paths()
def certidude_setup_strongswan_client(url, config, secrets, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, remote, auto, dpdaction):
from certidude.helpers import certidude_request_certificate
2015-08-13 08:11:08 +00:00
retval = certidude_request_certificate(
url,
key_path,
request_path,
certificate_path,
authority_path,
common_name,
org_unit,
email_address,
wait=True)
if retval:
return retval
# TODO: Add dhparam
config.write(env.get_template("strongswan-client-to-site.conf").render(locals()))
secrets.write(": RSA %s\n" % key_path)
2015-08-13 08:11:08 +00:00
click.echo("Generated %s and %s" % (config.name, secrets.name))
2015-08-13 08:11:08 +00:00
click.echo()
click.echo("Inspect newly created %s and start strongSwan service:" % config.name)
click.echo()
click.echo(" apt-get install strongswan strongswan-starter")
click.echo(" service strongswan restart")
click.echo()
@click.command("networkmanager", help="Set up strongSwan client via NetworkManager")
@click.argument("url")
@click.argument("remote")
@click.option("--common-name", "-cn", default=HOSTNAME, help="Common name, %s by default" % HOSTNAME)
@click.option("--org-unit", "-ou", help="Organizational unit")
@click.option("--email-address", "-m", default=EMAIL, help="E-mail associated with the request, '%s' by default" % EMAIL)
@click.option("--directory", "-d", default="/etc/ipsec.d", help="Directory for keys, /etc/ipsec.d by default")
@click.option("--key-path", "-key", default="private/%s.pem" % HOSTNAME, help="Key path, private/%s.pem by default" % HOSTNAME)
@click.option("--request-path", "-csr", default="reqs/%s.pem" % HOSTNAME, help="Request path, reqs/%s.pem by default" % HOSTNAME)
@click.option("--certificate-path", "-crt", default="certs/%s.pem" % HOSTNAME, help="Certificate path, certs/%s.pem by default" % HOSTNAME)
@click.option("--authority-path", "-ca", default="cacerts/ca.pem", help="Certificate authority certificate path, cacerts/ca.pem by default")
@expand_paths()
def certidude_setup_strongswan_networkmanager(url, email_address, common_name, org_unit, directory, key_path, request_path, certificate_path, authority_path, remote):
from certidude.helpers import certidude_request_certificate
retval = certidude_request_certificate(
url,
key_path,
request_path,
certificate_path,
authority_path,
common_name,
org_unit,
email_address,
wait=True)
if retval:
return retval
csummer = hashlib.sha1()
csummer.update(remote.encode("ascii"))
csum = csummer.hexdigest()
uuid = csum[:8] + "-" + csum[8:12] + "-" + csum[12:16] + "-" + csum[16:20] + "-" + csum[20:32]
config = configparser.ConfigParser()
config.add_section("connection")
config.add_section("vpn")
config.add_section("ipv4")
config.set("connection", "id", remote)
config.set("connection", "uuid", uuid)
config.set("connection", "type", "vpn")
config.set("connection", "autoconnect", "true")
config.set("vpn", "service-type", "org.freedesktop.NetworkManager.strongswan")
config.set("vpn", "userkey", key_path)
config.set("vpn", "usercert", certificate_path)
config.set("vpn", "encap", "no")
config.set("vpn", "address", remote)
config.set("vpn", "virtual", "yes")
config.set("vpn", "method", "key")
config.set("vpn", "certificate", authority_path)
config.set("vpn", "ipcomp", "no")
config.set("ipv4", "method", "auto")
# Prevent creation of files with liberal permissions
os.umask(0o277)
# Write keyfile
with open(os.path.join("/etc/NetworkManager/system-connections", remote), "w") as configfile:
config.write(configfile)
# TODO: Avoid race condition here
sleep(3)
# Tell NetworkManager to bring up the VPN connection
subprocess.call(("nmcli", "c", "up", "uuid", uuid))
2015-08-13 08:11:08 +00:00
@click.command("production", help="Set up nginx and uwsgi")
@click.option("--username", default="certidude", help="Service user account, created if necessary, 'certidude' by default")
@click.option("--hostname", default=HOSTNAME, help="nginx hostname, '%s' by default" % HOSTNAME)
@click.option("--static-path", default=os.path.join(os.path.dirname(__file__), "static"), help="Static files")
2016-01-10 17:51:54 +00:00
@click.option("--kerberos-keytab", default="/etc/certidude/server.keytab", help="Specify Kerberos keytab")
2015-08-13 08:11:08 +00:00
@click.option("--nginx-config", "-n",
default="/etc/nginx/nginx.conf",
type=click.File(mode="w", atomic=True, lazy=True),
help="nginx configuration, /etc/nginx/nginx.conf by default")
@click.option("--uwsgi-config", "-u",
default="/etc/uwsgi/apps-available/certidude.ini",
type=click.File(mode="w", atomic=True, lazy=True),
help="uwsgi configuration, /etc/uwsgi/ by default")
def certidude_setup_production(username, hostname, push_server, nginx_config, uwsgi_config, static_path, kerberos_keytab):
2015-08-13 08:11:08 +00:00
try:
pwd.getpwnam(username)
click.echo("Username '%s' already exists, excellent!" % username)
except KeyError:
cmd = "adduser", "--system", "--no-create-home", "--group", username
subprocess.check_call(cmd)
if subprocess.call("net ads testjoin", shell=True):
click.echo("Domain membership check failed, 'net ads testjoin' returned non-zero value", stderr=True)
exit(255)
if not os.path.exists(kerberos_keytab):
subprocess.call("KRB5_KTNAME=FILE:" + kerberos_keytab + " net ads keytab add HTTP -P")
click.echo("Created Kerberos keytab in '%s'" % kerberos_keytab)
2015-08-13 08:11:08 +00:00
if not static_path.endswith("/"):
static_path += "/"
nginx_config.write(env.get_template("nginx.conf").render(locals()))
click.echo("Generated: %s" % nginx_config.name)
uwsgi_config.write(env.get_template("uwsgi.ini").render(locals()))
click.echo("Generated: %s" % uwsgi_config.name)
if os.path.exists("/etc/uwsgi/apps-enabled/certidude.ini"):
os.unlink("/etc/uwsgi/apps-enabled/certidude.ini")
os.symlink(uwsgi_config.name, "/etc/uwsgi/apps-enabled/certidude.ini")
click.echo("Symlinked %s -> /etc/uwsgi/apps-enabled/certidude.ini" % uwsgi_config.name)
if not push_server:
click.echo("Remember to install nginx with wandenberg/nginx-push-stream-module!")
2015-07-26 20:34:46 +00:00
@click.command("authority", help="Set up Certificate Authority in a directory")
2015-07-12 19:22:10 +00:00
@click.option("--parent", "-p", help="Parent CA, none by default")
@click.option("--common-name", "-cn", default=FQDN, help="Common name, fully qualified hostname by default")
@click.option("--country", "-c", default=None, help="Country, none by default")
@click.option("--state", "-s", default=None, help="State or country, none by default")
@click.option("--locality", "-l", default=None, help="City or locality, none by default")
2015-08-13 08:11:08 +00:00
@click.option("--authority-lifetime", default=20*365, help="Authority certificate lifetime in days, 7300 days (20 years) by default")
@click.option("--certificate-lifetime", default=5*365, help="Certificate lifetime in days, 1825 days (5 years) by default")
@click.option("--revocation-list-lifetime", default=1, help="Revocation list lifetime in days, 1 day by default")
@click.option("--organization", "-o", default=None, help="Company or organization name")
@click.option("--organizational-unit", "-ou", default=None)
2015-07-12 19:22:10 +00:00
@click.option("--pkcs11", default=False, is_flag=True, help="Use PKCS#11 token instead of files")
2015-07-26 20:34:46 +00:00
@click.option("--crl-distribution-url", default=None, help="CRL distribution URL")
@click.option("--ocsp-responder-url", default=None, help="OCSP responder URL")
@click.option("--push-server", default="", help="Streaming nginx push server")
@click.option("--email-address", default="certidude@" + FQDN, help="E-mail address of the CA")
@click.option("--directory", default=os.path.join("/var/lib/certidude", FQDN), help="Directory for authority files, /var/lib/certidude/ by default")
def certidude_setup_authority(parent, country, state, locality, organization, organizational_unit, common_name, directory, certificate_lifetime, authority_lifetime, revocation_list_lifetime, pkcs11, crl_distribution_url, ocsp_responder_url, push_server, email_address):
# Make sure common_name is valid
if not re.match(r"^[\.\-_a-zA-Z0-9]+$", common_name):
raise click.ClickException("CA name can contain only alphanumeric, '_' and '-' characters")
if os.path.lexists(directory):
raise click.ClickException("Output directory {} already exists.".format(directory))
2016-01-10 17:51:54 +00:00
certidude_conf = os.path.join("/etc/certidude/server.conf")
if os.path.exists(certidude_conf):
raise click.ClickException("Configuration file %s already exists" % certidude_conf)
click.echo("CA configuration files are saved to: {}".format(directory))
2015-07-12 19:22:10 +00:00
click.echo("Generating 4096-bit RSA key...")
2015-07-26 20:34:46 +00:00
2015-07-12 19:22:10 +00:00
if pkcs11:
raise NotImplementedError("Hardware token support not yet implemented!")
else:
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 4096)
2015-07-26 20:34:46 +00:00
if not crl_distribution_url:
crl_distribution_url = "http://%s/api/revoked/" % common_name
2015-07-26 20:34:46 +00:00
# File paths
ca_key = os.path.join(directory, "ca_key.pem")
ca_crt = os.path.join(directory, "ca_crt.pem")
ca_crl = os.path.join(directory, "ca_crl.pem")
crl_distribution_points = "URI:%s" % crl_distribution_url
2015-07-12 19:22:10 +00:00
ca = crypto.X509()
2015-08-13 08:11:08 +00:00
ca.set_version(2) # This corresponds to X.509v3
2015-07-12 19:22:10 +00:00
ca.set_serial_number(1)
ca.get_subject().CN = common_name
if country:
ca.get_subject().C = country
if state:
ca.get_subject().ST = state
if locality:
ca.get_subject().L = locality
if organization:
ca.get_subject().O = organization
if organizational_unit:
ca.get_subject().OU = organizational_unit
2015-07-12 19:22:10 +00:00
ca.gmtime_adj_notBefore(0)
2015-08-13 08:11:08 +00:00
ca.gmtime_adj_notAfter(authority_lifetime * 24 * 60 * 60)
2015-07-12 19:22:10 +00:00
ca.set_issuer(ca.get_subject())
ca.set_pubkey(key)
ca.add_extensions([
crypto.X509Extension(
b"basicConstraints",
True,
2015-07-26 20:34:46 +00:00
b"CA:TRUE"),
2015-07-12 19:22:10 +00:00
crypto.X509Extension(
b"keyUsage",
True,
b"keyCertSign, cRLSign"),
2016-01-10 17:51:54 +00:00
crypto.X509Extension(
b"extendedKeyUsage",
True,
b"serverAuth,1.3.6.1.5.5.8.2.2"),
2015-07-12 19:22:10 +00:00
crypto.X509Extension(
b"subjectKeyIdentifier",
False,
b"hash",
subject = ca),
crypto.X509Extension(
b"crlDistributionPoints",
False,
crl_distribution_points.encode("ascii"))
])
subject_alt_name = "email:%s" % email_address
ca.add_extensions([
crypto.X509Extension(
b"subjectAltName",
False,
subject_alt_name.encode("ascii"))
])
2016-01-10 17:51:54 +00:00
ca.add_extensions([
crypto.X509Extension(
b"subjectAltName",
True,
("DNS:%s" % common_name).encode("ascii"))
])
2015-07-26 20:34:46 +00:00
2015-08-13 08:11:08 +00:00
if ocsp_responder_url:
raise NotImplementedError()
"""
ocsp_responder_url = "http://%s/api/ocsp/" % common_name
2015-07-26 20:34:46 +00:00
authority_info_access = "OCSP;URI:%s" % ocsp_responder_url
ca.add_extensions([
crypto.X509Extension(
b"authorityInfoAccess",
False,
authority_info_access.encode("ascii"))
])
2015-08-13 08:11:08 +00:00
"""
2015-07-26 20:34:46 +00:00
click.echo("Signing %s..." % ca.get_subject())
2015-07-26 20:34:46 +00:00
# openssl x509 -in ca_crt.pem -outform DER | sha256sum
2015-07-26 20:34:46 +00:00
# openssl x509 -fingerprint -in ca_crt.pem
ca.sign(key, "sha256")
2015-07-26 20:34:46 +00:00
2016-01-10 17:51:54 +00:00
_, _, uid, gid, gecos, root, shell = pwd.getpwnam("certidude")
os.setgid(gid)
# Create authority directory with 750 permissions
2015-07-26 20:34:46 +00:00
os.umask(0o027)
2015-07-12 19:22:10 +00:00
if not os.path.exists(directory):
os.makedirs(directory)
2015-07-26 20:34:46 +00:00
2016-01-10 17:51:54 +00:00
# Create subdirectories with 770 permissions
2015-07-26 20:34:46 +00:00
os.umask(0o007)
2015-07-12 19:22:10 +00:00
for subdir in ("signed", "requests", "revoked"):
if not os.path.exists(os.path.join(directory, subdir)):
os.mkdir(os.path.join(directory, subdir))
2016-01-10 17:51:54 +00:00
# Create CRL and serial file with 644 permissions
os.umask(0o133)
2015-07-26 20:34:46 +00:00
with open(ca_crl, "wb") as fh:
2015-07-12 19:22:10 +00:00
crl = crypto.CRL()
2015-08-13 08:11:08 +00:00
fh.write(crl.export(ca, key, days=revocation_list_lifetime))
2015-07-12 19:22:10 +00:00
with open(os.path.join(directory, "serial"), "w") as fh:
fh.write("1")
2016-01-10 17:51:54 +00:00
# Set permission bits to 640
os.umask(0o137)
with open(certidude_conf, "w") as fh:
fh.write(env.get_template("certidude.conf").render(locals()))
2015-07-26 20:34:46 +00:00
with open(ca_crt, "wb") as fh:
fh.write(crypto.dump_certificate(crypto.FILETYPE_PEM, ca))
2016-01-10 17:51:54 +00:00
# Set permission bits to 600
os.umask(0o177)
2015-07-26 20:34:46 +00:00
with open(ca_key, "wb") as fh:
fh.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
2015-07-12 19:22:10 +00:00
click.echo()
click.echo("Use following commands to inspect the newly created files:")
click.echo()
click.echo(" openssl crl -inform PEM -text -noout -in %s | less" % ca_crl)
click.echo(" openssl x509 -text -noout -in %s | less" % ca_crt)
2015-07-26 20:34:46 +00:00
click.echo(" openssl rsa -check -in %s" % ca_key)
click.echo(" openssl verify -CAfile %s %s" % (ca_crt, ca_crt))
click.echo()
click.echo("Use following to launch privilege isolated signer processes:")
click.echo()
2016-01-15 09:18:27 +00:00
click.echo(" certidude signer spawn -k")
2015-07-12 19:22:10 +00:00
click.echo()
click.echo("Use following command to serve CA read-only:")
click.echo()
click.echo(" certidude serve")
2015-07-26 20:34:46 +00:00
@click.command("list", help="List certificates")
2015-10-28 08:51:52 +00:00
@click.option("--verbose", "-v", default=False, is_flag=True, help="Verbose output")
2015-07-26 20:34:46 +00:00
@click.option("--show-key-type", "-k", default=False, is_flag=True, help="Show key type and length")
@click.option("--show-path", "-p", default=False, is_flag=True, help="Show filesystem paths")
@click.option("--show-extensions", "-e", default=False, is_flag=True, help="Show X.509 Certificate Extensions")
2015-10-28 08:51:52 +00:00
@click.option("--hide-requests", "-h", default=False, is_flag=True, help="Hide signing requests")
@click.option("--show-signed", "-s", default=False, is_flag=True, help="Show signed certificates")
@click.option("--show-revoked", "-r", default=False, is_flag=True, help="Show revoked certificates")
def certidude_list(verbose, show_key_type, show_extensions, show_path, show_signed, show_revoked, hide_requests):
2015-10-28 08:51:52 +00:00
# Statuses:
# s - submitted
# v - valid
# e - expired
# y - not valid yet
# r - revoked
from certidude import authority
2015-07-26 20:34:46 +00:00
from pycountry import countries
def dump_common(j):
person = [j for j in (j.given_name, j.surname) if j]
if person:
2015-10-28 08:51:52 +00:00
click.echo("Associated person: %s" % " ".join(person) + (" <%s>" % j.email_address if j.email_address else ""))
2015-07-26 20:34:46 +00:00
elif j.email_address:
2015-10-28 08:51:52 +00:00
click.echo("Associated e-mail: " + j.email_address)
2015-07-26 20:34:46 +00:00
bits = [j for j in (
countries.get(alpha2=j.country_code.upper()).name if
j.country_code else "",
j.state_or_county,
j.city,
j.organization,
j.organizational_unit) if j]
if bits:
2015-10-28 08:51:52 +00:00
click.echo("Organization: %s" % ", ".join(bits))
2015-07-26 20:34:46 +00:00
if show_key_type:
2015-10-28 08:51:52 +00:00
click.echo("Key type: %s-bit %s" % (j.key_length, j.key_type))
2015-07-26 20:34:46 +00:00
if show_extensions:
for key, value, data in j.extensions:
2015-10-28 08:51:52 +00:00
click.echo(("Extension " + key + ":").ljust(50) + " " + value)
else:
if j.key_usage:
click.echo("Key usage: " + j.key_usage)
if j.fqdn:
click.echo("Associated hostname: " + j.fqdn)
2015-07-26 20:34:46 +00:00
2015-10-28 08:51:52 +00:00
if not hide_requests:
for j in authority.list_requests():
if not verbose:
click.echo("s " + j.path + " " + j.identity)
continue
click.echo(click.style(j.common_name, fg="blue"))
click.echo("=" * len(j.common_name))
click.echo("State: ? " + click.style("submitted", fg="yellow") + " " + naturaltime(j.created) + click.style(", %s" %j.created, fg="white"))
dump_common(j)
# Calculate checksums for cross-checking
import hashlib
md5sum = hashlib.md5()
sha1sum = hashlib.sha1()
sha256sum = hashlib.sha256()
with open(j.path, "rb") as fh:
buf = fh.read()
md5sum.update(buf)
sha1sum.update(buf)
sha256sum.update(buf)
click.echo("MD5 checksum: %s" % md5sum.hexdigest())
click.echo("SHA-1 checksum: %s" % sha1sum.hexdigest())
click.echo("SHA-256 checksum: %s" % sha256sum.hexdigest())
if show_path:
click.echo("Details: openssl req -in %s -text -noout" % j.path)
click.echo("Sign: certidude sign %s" % j.path)
click.echo()
if show_signed:
for j in authority.list_signed():
if not verbose:
2015-10-28 08:51:52 +00:00
if j.signed < NOW and j.expires > NOW:
click.echo("v " + j.path + " " + j.identity)
2015-10-28 08:51:52 +00:00
elif NOW > j.expires:
click.echo("e " + j.path + " " + j.identity)
2015-10-28 08:51:52 +00:00
else:
click.echo("y " + j.path + " " + j.identity)
continue
2015-07-12 19:22:10 +00:00
click.echo(click.style(j.common_name, fg="blue") + " " + click.style(j.serial_number_hex, fg="white"))
click.echo("="*(len(j.common_name)+60))
2015-07-12 19:22:10 +00:00
if j.signed < NOW and j.expires > NOW:
click.echo("Status: \u2713 " + click.style("valid", fg="green") + " " + naturaltime(j.expires) + click.style(", %s" %j.expires, fg="white"))
elif NOW > j.expires:
click.echo("Status: \u2717 " + click.style("expired", fg="red") + " " + naturaltime(j.expires) + click.style(", %s" %j.expires, fg="white"))
else:
click.echo("Status: \u2717 " + click.style("not valid yet", fg="red") + click.style(", %s" %j.expires, fg="white"))
dump_common(j)
if show_path:
click.echo("Details: openssl x509 -in %s -text -noout" % j.path)
click.echo("Revoke: certidude revoke %s" % j.path)
click.echo()
if show_revoked:
for j in authority.list_revoked():
if not verbose:
click.echo("r " + j.path + " " + j.identity)
continue
click.echo(click.style(j.common_name, fg="blue") + " " + click.style(j.serial_number_hex, fg="white"))
click.echo("="*(len(j.common_name)+60))
click.echo("Status: \u2717 " + click.style("revoked", fg="red") + " %s%s" % (naturaltime(NOW-j.changed), click.style(", %s" % j.changed, fg="white")))
dump_common(j)
if show_path:
click.echo("Details: openssl x509 -in %s -text -noout" % j.path)
click.echo()
2015-07-26 20:34:46 +00:00
click.echo()
2015-07-12 19:22:10 +00:00
2015-07-26 20:34:46 +00:00
@click.command("sign", help="Sign certificates")
@click.argument("common_name")
@click.option("--overwrite", "-o", default=False, is_flag=True, help="Revoke valid certificate with same CN")
@click.option("--lifetime", "-l", help="Lifetime")
def certidude_sign(common_name, overwrite, lifetime):
from certidude import authority
request = authority.get_request(common_name)
if request.signable:
# Sign via signer process
cert = authority.sign(request)
else:
# Sign directly using private key
cert = authority.sign2(request, overwrite, True, lifetime)
2015-08-13 08:11:08 +00:00
click.echo("Signed %s" % cert.identity)
for key, value, data in cert.extensions:
click.echo("Added extension %s: %s" % (key, value))
click.echo()
2015-07-26 20:34:46 +00:00
2015-07-12 19:22:10 +00:00
@click.command("serve", help="Run built-in HTTP server")
2015-07-26 20:34:46 +00:00
@click.option("-u", "--user", default="certidude", help="Run as user")
2015-07-12 19:22:10 +00:00
@click.option("-p", "--port", default=80, help="Listen port")
@click.option("-l", "--listen", default="0.0.0.0", help="Listen address")
@click.option("-s", "--enable-signature", default=False, is_flag=True, help="Allow signing operations with private key of CA")
2015-07-26 20:34:46 +00:00
def certidude_serve(user, port, listen, enable_signature):
logging.basicConfig(
filename='/var/log/certidude.log',
level=logging.DEBUG)
2015-07-12 19:22:10 +00:00
click.echo("Serving API at %s:%d" % (listen, port))
import pwd
from wsgiref.simple_server import make_server, WSGIServer
from socketserver import ThreadingMixIn
from certidude.api import certidude_app, StaticResource
from certidude import config
2015-07-12 19:22:10 +00:00
2015-07-26 20:34:46 +00:00
class ThreadingWSGIServer(ThreadingMixIn, WSGIServer):
2015-07-12 19:22:10 +00:00
pass
2015-07-27 12:30:50 +00:00
2015-07-12 19:22:10 +00:00
click.echo("Listening on %s:%d" % (listen, port))
2015-07-26 20:34:46 +00:00
app = certidude_app()
2015-08-13 08:11:08 +00:00
app.add_sink(StaticResource(os.path.join(os.path.dirname(__file__), "static")))
2015-07-12 19:22:10 +00:00
httpd = make_server(listen, port, app, ThreadingWSGIServer)
2015-09-03 09:00:45 +00:00
2015-07-12 19:22:10 +00:00
if user:
2015-09-03 09:00:45 +00:00
# Load required utils which cannot be imported from chroot
# TODO: Figure out better approach
from jinja2.debug import make_traceback as _make_traceback
"".encode("charmap")
if config.AUTHENTICATION_BACKEND == "pam":
# PAM needs access to /etc/shadow
import grp
name, passwd, gid, mem = grp.getgrnam("shadow")
click.echo("Adding current user to shadow group due to PAM authentication backend")
os.setgroups([gid])
else:
os.setgroups([])
2015-07-12 19:22:10 +00:00
_, _, uid, gid, gecos, root, shell = pwd.getpwnam(user)
if uid == 0:
click.echo("Please specify unprivileged user")
exit(254)
2015-07-12 19:22:10 +00:00
os.setgid(gid)
os.setuid(uid)
click.echo("Switched to user %s (uid=%d, gid=%d); member of groups %s" %
(user, uid, gid, ", ".join([str(j) for j in os.getgroups()])))
2015-07-26 20:34:46 +00:00
os.umask(0o007)
2015-07-12 19:22:10 +00:00
elif os.getuid() == 0:
2015-09-03 09:00:45 +00:00
click.echo("Warning: running as root, this is not recommended!")
2015-07-12 19:22:10 +00:00
httpd.serve_forever()
2015-08-13 08:11:08 +00:00
@click.group("strongswan", help="strongSwan helpers")
def certidude_setup_strongswan(): pass
2015-07-26 20:34:46 +00:00
@click.group("openvpn", help="OpenVPN helpers")
def certidude_setup_openvpn(): pass
2015-07-12 19:22:10 +00:00
2015-07-26 20:34:46 +00:00
@click.group("setup", help="Getting started section")
def certidude_setup(): pass
2015-07-12 19:22:10 +00:00
2016-01-15 09:18:27 +00:00
@click.group("signer", help="Signer process management")
def certidude_signer(): pass
@click.group("request", help="CSR process management")
def certidude_request(): pass
2015-07-12 19:22:10 +00:00
@click.group()
def entry_point(): pass
2015-08-13 08:11:08 +00:00
certidude_setup_strongswan.add_command(certidude_setup_strongswan_server)
certidude_setup_strongswan.add_command(certidude_setup_strongswan_client)
certidude_setup_strongswan.add_command(certidude_setup_strongswan_networkmanager)
2015-07-26 20:34:46 +00:00
certidude_setup_openvpn.add_command(certidude_setup_openvpn_server)
certidude_setup_openvpn.add_command(certidude_setup_openvpn_client)
certidude_setup.add_command(certidude_setup_authority)
certidude_setup.add_command(certidude_setup_openvpn)
2015-08-13 08:11:08 +00:00
certidude_setup.add_command(certidude_setup_strongswan)
2015-07-26 20:34:46 +00:00
certidude_setup.add_command(certidude_setup_client)
2015-08-13 08:11:08 +00:00
certidude_setup.add_command(certidude_setup_production)
2016-01-15 09:18:27 +00:00
certidude_request.add_command(certidude_request_spawn)
certidude_signer.add_command(certidude_signer_spawn)
2015-07-26 20:34:46 +00:00
entry_point.add_command(certidude_setup)
entry_point.add_command(certidude_serve)
2016-01-15 09:18:27 +00:00
entry_point.add_command(certidude_signer)
entry_point.add_command(certidude_request)
2015-07-26 20:34:46 +00:00
entry_point.add_command(certidude_sign)
entry_point.add_command(certidude_list)