Initial commit
This commit is contained in:
commit
4eb2c17652
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
__pycache__
|
||||
*~
|
||||
*.swp
|
541
cli.py
Normal file
541
cli.py
Normal file
@ -0,0 +1,541 @@
|
||||
# coding: utf-8
|
||||
|
||||
import click
|
||||
import const
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import signal
|
||||
import string
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import requests
|
||||
from jinja2 import Environment, PackageLoader
|
||||
from ipsecparse import loads
|
||||
from asn1crypto import pem, x509
|
||||
from asn1crypto.csr import CertificationRequest
|
||||
from certbuilder import CertificateBuilder, pem_armor_certificate
|
||||
from csrbuilder import CSRBuilder, pem_armor_csr
|
||||
from configparser import ConfigParser, NoOptionError
|
||||
from datetime import datetime, timedelta
|
||||
from oscrypto import asymmetric
|
||||
|
||||
class ConfigTreeParser(ConfigParser):
|
||||
def __init__(self, path, *args, **kwargs):
|
||||
ConfigParser.__init__(self, *args, **kwargs)
|
||||
if os.path.exists(path):
|
||||
with open(path) as fh:
|
||||
click.echo("Parsing: %s" % fh.name)
|
||||
self.read_file(fh)
|
||||
if os.path.exists(path + ".d"):
|
||||
for filename in os.listdir(path + ".d"):
|
||||
if not filename.endswith(".conf"):
|
||||
continue
|
||||
with open(os.path.join(path + ".d", filename)) as fh:
|
||||
click.echo("Parsing: %s" % fh.name)
|
||||
self.read_file(fh)
|
||||
|
||||
|
||||
@click.command("provision", help="Add endpoint to Certidude client config")
|
||||
@click.argument("authority")
|
||||
def certidude_provision(authority):
|
||||
client_config = ConfigParser()
|
||||
if os.path.exists(const.CLIENT_CONFIG_PATH):
|
||||
client_config.read_file(open(const.CLIENT_CONFIG_PATH))
|
||||
if client_config.has_section(authority):
|
||||
click.echo("Section '%s' already exists in %s, remove to regenerate" % (authority, const.CLIENT_CONFIG_PATH))
|
||||
else:
|
||||
click.echo("Section '%s' added to %s" % (authority, const.CLIENT_CONFIG_PATH))
|
||||
b = os.path.join(os.path.join(const.CONFIG_DIR, "authority", authority))
|
||||
client_config.add_section(authority)
|
||||
client_config.set(authority, "trigger", "interface up")
|
||||
client_config.set(authority, "common name", "$HOSTNAME")
|
||||
client_config.set(authority, "request path", os.path.join(b, "host_req.pem"))
|
||||
client_config.set(authority, "key path", os.path.join(b, "host_key.pem"))
|
||||
client_config.set(authority, "certificate path", os.path.join(b, "host_cert.pem"))
|
||||
client_config.set(authority, "authority path", os.path.join(b, "ca_cert.pem"))
|
||||
with open(const.CLIENT_CONFIG_PATH + ".part", 'w') as fh:
|
||||
client_config.write(fh)
|
||||
os.rename(const.CLIENT_CONFIG_PATH + ".part", const.CLIENT_CONFIG_PATH)
|
||||
|
||||
|
||||
@click.command("enroll", help="Run processes for requesting certificates and configuring services")
|
||||
@click.option("-k", "--kerberos", default=False, is_flag=True, help="Offer system keytab for auth")
|
||||
@click.option("-f", "--fork", default=False, is_flag=True, help="Fork to background")
|
||||
@click.option("-nw", "--no-wait", default=False, is_flag=True, help="Return immideately if server doesn't autosign")
|
||||
def certidude_enroll(fork, no_wait, kerberos):
|
||||
try:
|
||||
os.makedirs(const.RUN_DIR)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
context = globals()
|
||||
context.update(locals())
|
||||
|
||||
if not os.path.exists(const.CLIENT_CONFIG_PATH):
|
||||
click.echo("Client not configured, so not going to enroll")
|
||||
return
|
||||
|
||||
clients = ConfigTreeParser(const.CLIENT_CONFIG_PATH)
|
||||
service_config = ConfigTreeParser(const.SERVICES_CONFIG_PATH)
|
||||
|
||||
for authority_name in clients.sections():
|
||||
try:
|
||||
trigger = clients.get(authority_name, "trigger")
|
||||
except NoOptionError:
|
||||
trigger = "interface up"
|
||||
|
||||
if trigger == "domain joined":
|
||||
# Stop further processing if command line argument said so or trigger expects domain membership
|
||||
if not os.path.exists("/etc/krb5.keytab"):
|
||||
continue
|
||||
kerberos = True
|
||||
elif trigger == "interface up":
|
||||
pass
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
#########################
|
||||
### Fork if requested ###
|
||||
#########################
|
||||
|
||||
pid_path = os.path.join(const.RUN_DIR, authority_name + ".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 EnvironmentError:
|
||||
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())
|
||||
|
||||
try:
|
||||
authority_path = clients.get(authority_name, "authority path")
|
||||
except NoOptionError:
|
||||
authority_path = "/etc/certidude/authority/%s/ca_cert.pem" % authority_name
|
||||
finally:
|
||||
if os.path.exists(authority_path):
|
||||
click.echo("Found authority certificate in: %s" % authority_path)
|
||||
with open(authority_path, "rb") as fh:
|
||||
header, _, certificate_der_bytes = pem.unarmor(fh.read())
|
||||
authority_certificate = x509.Certificate.load(certificate_der_bytes)
|
||||
else:
|
||||
if not os.path.exists(os.path.dirname(authority_path)):
|
||||
os.makedirs(os.path.dirname(authority_path))
|
||||
authority_url = "http://%s/api/certificate/" % authority_name
|
||||
click.echo("Attempting to fetch authority certificate from %s" % authority_url)
|
||||
try:
|
||||
r = requests.get(authority_url,
|
||||
headers={"Accept": "application/x-x509-ca-cert,application/x-pem-file"})
|
||||
header, _, certificate_der_bytes = pem.unarmor(r.content)
|
||||
authority_certificate = x509.Certificate.load(certificate_der_bytes)
|
||||
except: # TODO: catch correct exceptions
|
||||
raise
|
||||
# raise ValueError("Failed to parse PEM: %s" % r.text)
|
||||
authority_partial = authority_path + ".part"
|
||||
with open(authority_partial, "wb") as oh:
|
||||
oh.write(r.content)
|
||||
click.echo("Writing authority certificate to: %s" % authority_path)
|
||||
selinux_fixup(authority_partial)
|
||||
os.rename(authority_partial, authority_path)
|
||||
|
||||
authority_public_key = asymmetric.load_public_key(
|
||||
authority_certificate["tbs_certificate"]["subject_public_key_info"])
|
||||
|
||||
|
||||
|
||||
# Attempt to install CA certificates system wide
|
||||
try:
|
||||
authority_system_wide = clients.getboolean(authority_name, "system wide")
|
||||
except NoOptionError:
|
||||
authority_system_wide = False
|
||||
finally:
|
||||
if authority_system_wide:
|
||||
# Firefox, Chromium, wget, curl on Fedora
|
||||
# Note that if ~/.pki/nssdb has been customized before, curl breaks
|
||||
if os.path.exists("/usr/bin/update-ca-trust"):
|
||||
link_path = "/etc/pki/ca-trust/source/anchors/%s" % authority_name
|
||||
if not os.path.lexists(link_path):
|
||||
os.symlink(authority_path, link_path)
|
||||
os.system("update-ca-trust")
|
||||
|
||||
# curl on Fedora ?
|
||||
# pip
|
||||
|
||||
# Firefox (?) on Debian, Ubuntu
|
||||
if os.path.exists("/usr/bin/update-ca-certificates") or os.path.exists("/usr/sbin/update-ca-certificates"):
|
||||
link_path = "/usr/local/share/ca-certificates/%s" % authority_name
|
||||
if not os.path.lexists(link_path):
|
||||
os.symlink(authority_path, link_path)
|
||||
os.system("update-ca-certificates")
|
||||
|
||||
# TODO: test for curl, wget
|
||||
|
||||
try:
|
||||
common_name = clients.get(authority_name, "common name")
|
||||
except NoOptionError:
|
||||
click.echo("No common name specified for %s, not requesting a certificate" % authority_name)
|
||||
continue
|
||||
|
||||
# If deriving common name from *current* hostname is preferred
|
||||
if common_name == "$HOSTNAME":
|
||||
common_name = const.HOSTNAME
|
||||
elif common_name == "$FQDN":
|
||||
common_name = const.FQDN
|
||||
elif "$" in common_name:
|
||||
raise ValueError("Invalid variable '%s' supplied, only $HOSTNAME and $FQDN allowed" % common_name)
|
||||
if not re.match(const.RE_COMMON_NAME, common_name):
|
||||
raise ValueError("Supplied common name %s doesn't match the expression %s" % (common_name, const.RE_COMMON_NAME))
|
||||
|
||||
|
||||
################################
|
||||
### Generate keypair and CSR ###
|
||||
################################
|
||||
|
||||
try:
|
||||
key_path = clients.get(authority_name, "key path")
|
||||
request_path = clients.get(authority_name, "request path")
|
||||
except NoOptionError:
|
||||
key_path = "/etc/certidude/authority/%s/host_key.pem" % authority_name
|
||||
request_path = "/etc/certidude/authority/%s/host_csr.pem" % authority_name
|
||||
|
||||
if os.path.exists(request_path):
|
||||
with open(request_path, "rb") as fh:
|
||||
header, _, der_bytes = pem.unarmor(fh.read())
|
||||
csr = CertificationRequest.load(der_bytes)
|
||||
if csr["certification_request_info"]["subject"].native["common_name"] != common_name:
|
||||
click.echo("Stored request's common name differs from currently requested one, deleting old request")
|
||||
os.remove(request_path)
|
||||
|
||||
if not os.path.exists(request_path):
|
||||
key_partial = key_path + ".part"
|
||||
request_partial = request_path + ".part"
|
||||
|
||||
if authority_public_key.algorithm == "ec":
|
||||
self_public_key, private_key = asymmetric.generate_pair("ec", curve=authority_public_key.curve)
|
||||
elif authority_public_key.algorithm == "rsa":
|
||||
self_public_key, private_key = asymmetric.generate_pair("rsa", bit_size=authority_public_key.bit_size)
|
||||
else:
|
||||
NotImplemented
|
||||
|
||||
builder = CSRBuilder({"common_name": common_name}, self_public_key)
|
||||
request = builder.build(private_key)
|
||||
with open(key_partial, 'wb') as f:
|
||||
f.write(asymmetric.dump_private_key(private_key, None))
|
||||
with open(request_partial, 'wb') as f:
|
||||
f.write(pem_armor_csr(request))
|
||||
selinux_fixup(key_partial)
|
||||
selinux_fixup(request_partial)
|
||||
os.rename(key_partial, key_path)
|
||||
os.rename(request_partial, request_path)
|
||||
|
||||
|
||||
##############################################
|
||||
### Submit CSR and save signed certificate ###
|
||||
##############################################
|
||||
|
||||
try:
|
||||
certificate_path = clients.get(authority_name, "certificate path")
|
||||
except NoOptionError:
|
||||
certificate_path = "/etc/certidude/authority/%s/host_cert.pem" % authority_name
|
||||
|
||||
try:
|
||||
autosign = clients.getboolean(authority_name, "autosign")
|
||||
except NoOptionError:
|
||||
autosign = True
|
||||
|
||||
if not os.path.exists(certificate_path):
|
||||
# Set up URL-s
|
||||
request_params = set()
|
||||
request_params.add("autosign=%s" % ("yes" if autosign else "no"))
|
||||
if not no_wait:
|
||||
request_params.add("wait=forever")
|
||||
|
||||
kwargs = {
|
||||
"data": open(request_path),
|
||||
"verify": authority_path,
|
||||
"headers": {
|
||||
"Content-Type": "application/pkcs10",
|
||||
"Accept": "application/x-x509-user-cert,application/x-pem-file"
|
||||
}
|
||||
}
|
||||
|
||||
# If machine is joined to domain attempt to present machine credentials for authentication
|
||||
if kerberos:
|
||||
try:
|
||||
from requests_kerberos import HTTPKerberosAuth, OPTIONAL
|
||||
except ImportError:
|
||||
click.echo("Kerberos bindings not available, please install requests-kerberos")
|
||||
else:
|
||||
os.environ["KRB5CCNAME"]="/tmp/ca.ticket"
|
||||
|
||||
# Mac OS X has keytab with lowercase hostname
|
||||
cmd = "kinit -S HTTP/%s -k %s$" % (authority_name, const.HOSTNAME.lower())
|
||||
click.echo("Executing: %s" % cmd)
|
||||
if os.system(cmd):
|
||||
# Fedora /w SSSD has keytab with uppercase hostname
|
||||
cmd = "kinit -S HTTP/%s -k %s$" % (authority_name, const.HOSTNAME.upper())
|
||||
if os.system(cmd):
|
||||
# Failed, probably /etc/krb5.keytab contains spaghetti
|
||||
raise ValueError("Failed to initialize Kerberos service ticket using machine keytab")
|
||||
assert os.path.exists("/tmp/ca.ticket"), "Ticket not created!"
|
||||
click.echo("Initialized Kerberos service ticket using machine keytab")
|
||||
kwargs["auth"] = HTTPKerberosAuth(mutual_authentication=OPTIONAL, force_preemptive=True)
|
||||
else:
|
||||
click.echo("Not using machine keytab")
|
||||
|
||||
request_url = "https://%s:8443/api/request/" % authority_name
|
||||
if request_params:
|
||||
request_url = request_url + "?" + "&".join(request_params)
|
||||
submission = requests.post(request_url, **kwargs)
|
||||
|
||||
# Destroy service ticket
|
||||
if os.path.exists("/tmp/ca.ticket"):
|
||||
os.system("kdestroy")
|
||||
|
||||
if submission.status_code == requests.codes.ok:
|
||||
pass
|
||||
if submission.status_code == requests.codes.accepted:
|
||||
click.echo("Server accepted the request, but refused to sign immideately (%s). Waiting was not requested, hence quitting for now" % submission.text)
|
||||
os.unlink(pid_path)
|
||||
continue
|
||||
if submission.status_code == requests.codes.conflict:
|
||||
raise errors.DuplicateCommonNameError("Different signing request with same CN is already present on server, server refuses to overwrite")
|
||||
elif submission.status_code == requests.codes.gone:
|
||||
# Should the client retry or disable request submission?
|
||||
raise ValueError("Server refused to sign the request") # TODO: Raise proper exception
|
||||
elif submission.status_code == requests.codes.bad_request:
|
||||
raise ValueError("Server said following, likely current certificate expired/revoked? %s" % submission.text)
|
||||
else:
|
||||
submission.raise_for_status()
|
||||
|
||||
try:
|
||||
header, _, certificate_der_bytes = pem.unarmor(submission.content)
|
||||
cert = x509.Certificate.load(certificate_der_bytes)
|
||||
except: # TODO: catch correct exceptions
|
||||
raise ValueError("Failed to parse PEM: %s" % submission.text)
|
||||
|
||||
assert cert.subject.native["common_name"] == common_name, \
|
||||
"Expected certificate with common name %s, but got %s instead" % \
|
||||
(common_name, cert.subject.native["common_name"])
|
||||
|
||||
os.umask(0o022)
|
||||
certificate_partial = certificate_path + ".part"
|
||||
with open(certificate_partial, "w") as fh:
|
||||
# Dump certificate
|
||||
fh.write(submission.text)
|
||||
|
||||
click.echo("Writing certificate to: %s" % certificate_path)
|
||||
selinux_fixup(certificate_partial)
|
||||
os.rename(certificate_partial, certificate_path)
|
||||
|
||||
else:
|
||||
click.echo("Certificate found at %s and no renewal requested" % certificate_path)
|
||||
|
||||
|
||||
##################################
|
||||
### Configure related services ###
|
||||
##################################
|
||||
|
||||
for endpoint in service_config.sections():
|
||||
if service_config.get(endpoint, "authority") != authority_name:
|
||||
continue
|
||||
|
||||
click.echo("Configuring '%s'" % endpoint)
|
||||
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]
|
||||
|
||||
# Intranet HTTPS handled by PKCS#12 bundle generation,
|
||||
# so it will not be implemented here
|
||||
|
||||
# OpenVPN set up with initscripts
|
||||
if service_config.get(endpoint, "service") == "init/openvpn":
|
||||
if os.path.exists("/etc/openvpn/%s.disabled" % endpoint) and not os.path.exists("/etc/openvpn/%s.conf" % endpoint):
|
||||
os.rename("/etc/openvpn/%s.disabled" % endpoint, "/etc/openvpn/%s.conf" % endpoint)
|
||||
if os.path.exists("/bin/systemctl"):
|
||||
click.echo("Re-running systemd generators for OpenVPN...")
|
||||
os.system("systemctl daemon-reload")
|
||||
if not os.path.exists("/etc/systemd/system/openvpn-reconnect.service"):
|
||||
with open("/etc/systemd/system/openvpn-reconnect.service.part", "w") as fh:
|
||||
fh.write(env.get_template("client/openvpn-reconnect.service").render(context))
|
||||
os.rename("/etc/systemd/system/openvpn-reconnect.service.part",
|
||||
"/etc/systemd/system/openvpn-reconnect.service")
|
||||
click.echo("Created /etc/systemd/system/openvpn-reconnect.service")
|
||||
click.echo("Starting OpenVPN...")
|
||||
os.system("service openvpn start")
|
||||
continue
|
||||
|
||||
# IPSec set up with initscripts
|
||||
if service_config.get(endpoint, "service") == "init/strongswan":
|
||||
config = loads(open("%s/ipsec.conf" % const.STRONGSWAN_PREFIX).read())
|
||||
for section_type, section_name in config:
|
||||
# Identify correct ipsec.conf section by leftcert
|
||||
if section_type != "conn":
|
||||
continue
|
||||
if config[section_type,section_name]["leftcert"] != certificate_path:
|
||||
continue
|
||||
|
||||
if config[section_type,section_name].get("left", "") == "%defaultroute":
|
||||
config[section_type,section_name]["auto"] = "start" # This is client
|
||||
elif config[section_type,section_name].get("leftsourceip", ""):
|
||||
config[section_type,section_name]["auto"] = "add" # This is server
|
||||
else:
|
||||
config[section_type,section_name]["auto"] = "route" # This is site-to-site tunnel
|
||||
|
||||
with open("%s/ipsec.conf.part" % const.STRONGSWAN_PREFIX, "w") as fh:
|
||||
fh.write(config.dumps())
|
||||
os.rename(
|
||||
"%s/ipsec.conf.part" % const.STRONGSWAN_PREFIX,
|
||||
"%s/ipsec.conf" % const.STRONGSWAN_PREFIX)
|
||||
break
|
||||
|
||||
# Tune AppArmor profile, TODO: retain contents
|
||||
if os.path.exists("/etc/apparmor.d/local"):
|
||||
with open("/etc/apparmor.d/local/usr.lib.ipsec.charon", "w") as fh:
|
||||
fh.write(key_path + " r,\n")
|
||||
fh.write(authority_path + " r,\n")
|
||||
fh.write(certificate_path + " r,\n")
|
||||
|
||||
# Attempt to reload config or start if it's not running
|
||||
if os.path.exists("/usr/sbin/strongswan"): # wtf fedora
|
||||
if os.system("strongswan update"):
|
||||
os.system("strongswan start")
|
||||
else:
|
||||
if os.system("ipsec update"):
|
||||
os.system("ipsec start")
|
||||
|
||||
continue
|
||||
|
||||
# OpenVPN set up with NetworkManager
|
||||
if service_config.get(endpoint, "service") == "network-manager/openvpn":
|
||||
# NetworkManager-strongswan-gnome
|
||||
nm_config_path = os.path.join("/etc/NetworkManager/system-connections", endpoint)
|
||||
if os.path.exists(nm_config_path):
|
||||
click.echo("Not creating %s, remove to regenerate" % nm_config_path)
|
||||
continue
|
||||
nm_config = ConfigParser()
|
||||
nm_config.add_section("connection")
|
||||
nm_config.set("connection", "certidude managed", "true")
|
||||
nm_config.set("connection", "id", endpoint)
|
||||
nm_config.set("connection", "uuid", uuid)
|
||||
nm_config.set("connection", "type", "vpn")
|
||||
nm_config.add_section("vpn")
|
||||
nm_config.set("vpn", "service-type", "org.freedesktop.NetworkManager.openvpn")
|
||||
nm_config.set("vpn", "connection-type", "tls")
|
||||
nm_config.set("vpn", "comp-lzo", "no")
|
||||
nm_config.set("vpn", "cert-pass-flags", "0")
|
||||
nm_config.set("vpn", "tap-dev", "no")
|
||||
nm_config.set("vpn", "remote-cert-tls", "server") # Assert TLS Server flag of X.509 certificate
|
||||
nm_config.set("vpn", "remote", service_config.get(endpoint, "remote"))
|
||||
nm_config.set("vpn", "key", key_path)
|
||||
nm_config.set("vpn", "cert", certificate_path)
|
||||
nm_config.set("vpn", "ca", authority_path)
|
||||
nm_config.set("vpn", "tls-cipher", "TLS-%s-WITH-AES-256-GCM-SHA384" % (
|
||||
"ECDHE-ECDSA" if authority_public_key.algorithm == "ec" else "DHE-RSA"))
|
||||
nm_config.set("vpn", "cipher", "AES-128-GCM")
|
||||
nm_config.set("vpn", "auth", "SHA384")
|
||||
nm_config.add_section("ipv4")
|
||||
nm_config.set("ipv4", "method", "auto")
|
||||
nm_config.set("ipv4", "never-default", "true")
|
||||
nm_config.add_section("ipv6")
|
||||
nm_config.set("ipv6", "method", "auto")
|
||||
|
||||
try:
|
||||
nm_config.set("vpn", "port", str(service_config.getint(endpoint, "port")))
|
||||
except NoOptionError:
|
||||
nm_config.set("vpn", "port", "1194")
|
||||
|
||||
try:
|
||||
if service_config.get(endpoint, "proto") == "tcp":
|
||||
nm_config.set("vpn", "proto-tcp", "yes")
|
||||
except NoOptionError:
|
||||
pass
|
||||
|
||||
# Prevent creation of files with liberal permissions
|
||||
os.umask(0o177)
|
||||
|
||||
# Write NetworkManager configuration
|
||||
with open(nm_config_path, "w") as fh:
|
||||
nm_config.write(fh)
|
||||
click.echo("Created %s" % nm_config_path)
|
||||
if os.path.exists("/run/NetworkManager"):
|
||||
os.system("nmcli con reload")
|
||||
continue
|
||||
|
||||
|
||||
# IPSec set up with NetworkManager
|
||||
if service_config.get(endpoint, "service") == "network-manager/strongswan":
|
||||
client_config = ConfigParser()
|
||||
nm_config = ConfigParser()
|
||||
nm_config.add_section("connection")
|
||||
nm_config.set("connection", "certidude managed", "true")
|
||||
nm_config.set("connection", "id", endpoint)
|
||||
nm_config.set("connection", "uuid", uuid)
|
||||
nm_config.set("connection", "type", "vpn")
|
||||
nm_config.add_section("vpn")
|
||||
nm_config.set("vpn", "service-type", "org.freedesktop.NetworkManager.strongswan")
|
||||
nm_config.set("vpn", "encap", "no")
|
||||
nm_config.set("vpn", "virtual", "yes")
|
||||
nm_config.set("vpn", "method", "key")
|
||||
nm_config.set("vpn", "ipcomp", "no")
|
||||
nm_config.set("vpn", "address", service_config.get(endpoint, "remote"))
|
||||
nm_config.set("vpn", "userkey", key_path)
|
||||
nm_config.set("vpn", "usercert", certificate_path)
|
||||
nm_config.set("vpn", "certificate", authority_path)
|
||||
dhgroup = "ecp384" if authority_public_key.algorithm == "ec" else "modp2048"
|
||||
nm_config.set("vpn", "ike", "aes256-sha384-prfsha384-" + dhgroup)
|
||||
nm_config.set("vpn", "esp", "aes128gcm16-aes128gmac-" + dhgroup)
|
||||
nm_config.set("vpn", "proposal", "yes")
|
||||
|
||||
nm_config.add_section("ipv4")
|
||||
nm_config.set("ipv4", "method", "auto")
|
||||
|
||||
# Add routes, may need some more tweaking
|
||||
if service_config.has_option(endpoint, "route"):
|
||||
for index, subnet in enumerate(service_config.get(endpoint, "route").split(","), start=1):
|
||||
nm_config.set("ipv4", "route%d" % index, subnet)
|
||||
|
||||
# Prevent creation of files with liberal permissions
|
||||
os.umask(0o177)
|
||||
|
||||
# Write NetworkManager configuration
|
||||
with open(os.path.join("/etc/NetworkManager/system-connections", endpoint), "w") as fh:
|
||||
nm_config.write(fh)
|
||||
click.echo("Created %s" % fh.name)
|
||||
if os.path.exists("/run/NetworkManager"):
|
||||
os.system("nmcli con reload")
|
||||
continue
|
||||
|
||||
# TODO: Puppet, OpenLDAP, <insert awesomeness here>
|
||||
click.echo("Unknown service: %s" % service_config.get(endpoint, "service"))
|
||||
os.unlink(pid_path)
|
||||
|
||||
|
||||
@click.group()
|
||||
def entry_point(): pass
|
||||
|
||||
|
||||
entry_point.add_command(certidude_enroll)
|
||||
entry_point.add_command(certidude_provision)
|
||||
|
||||
if __name__ == "__main__":
|
||||
entry_point()
|
22
const.py
Normal file
22
const.py
Normal file
@ -0,0 +1,22 @@
|
||||
import os
|
||||
import socket
|
||||
|
||||
RUN_DIR = "/run/certidude"
|
||||
CONFIG_DIR = "/etc/certidude"
|
||||
CLIENT_CONFIG_PATH = os.path.join(CONFIG_DIR, "client.conf")
|
||||
SERVICES_CONFIG_PATH = os.path.join(CONFIG_DIR, "services.conf")
|
||||
|
||||
RE_FQDN = "^(([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])\.)+([a-z0-9]|[a-z0-9][a-z0-9\-_]*[a-z0-9])?$"
|
||||
RE_HOSTNAME = "^[a-z0-9]([a-z0-9\-_]{0,61}[a-z0-9])?$"
|
||||
RE_COMMON_NAME = "^[A-Za-z0-9\-\.\_@]+$"
|
||||
|
||||
try:
|
||||
FQDN = socket.getaddrinfo(socket.gethostname(), 0, socket.AF_INET, 0, 0, socket.AI_CANONNAME)[0][3]
|
||||
except socket.gaierror:
|
||||
FQDN = socket.gethostname()
|
||||
|
||||
try:
|
||||
HOSTNAME, DOMAIN = FQDN.split(".", 1)
|
||||
except ValueError: # If FQDN is not configured
|
||||
HOSTNAME = FQDN
|
||||
DOMAIN = None
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
asn1crypto
|
||||
certbuilder
|
||||
csrbuilder
|
||||
ipsecparse
|
Loading…
Reference in New Issue
Block a user