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
2015-10-17 15:07:26 +00:00
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
2016-09-17 21:00:14 +00:00
import random
2015-07-12 19:22:10 +00:00
import re
2016-01-14 22:47:30 +00:00
import requests
2015-07-26 20:34:46 +00:00
import signal
2015-08-13 08:11:08 +00:00
import socket
2016-09-17 21:00:14 +00:00
import string
2015-07-27 15:49:50 +00:00
import subprocess
2015-08-13 08:11:08 +00:00
import sys
2016-09-17 21:00:14 +00:00
from configparser import ConfigParser , NoOptionError , NoSectionError
2016-03-27 20:38:14 +00:00
from certidude . helpers import certidude_request_certificate
2016-03-21 21:42:39 +00:00
from certidude . common import expand_paths , ip_address , ip_network
2016-09-17 21:00:14 +00:00
from cryptography import x509
from cryptography . x509 . oid import NameOID , ExtendedKeyUsageOID
from cryptography . hazmat . backends import default_backend
from cryptography . hazmat . primitives import hashes , serialization
from cryptography . hazmat . primitives . asymmetric import rsa
2016-03-29 16:29:06 +00:00
from datetime import datetime , timedelta
2015-07-26 20:34:46 +00:00
from humanize import naturaltime
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
2016-09-17 21:00:14 +00:00
import const
2015-07-12 19:22:10 +00:00
2016-03-29 16:29:06 +00:00
env = Environment ( loader = PackageLoader ( " certidude " , " templates " ) , trim_blocks = True )
2015-07-26 20:34:46 +00:00
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/
2016-09-17 21:00:14 +00:00
# keyUsage, extendedKeyUsage - https://www.openssl.org/docs/apps/x509v3_client_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
2016-09-17 21:00:14 +00:00
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 :
2016-09-17 21:00:14 +00:00
EMAIL = USERNAME + " @ " + const . 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
2015-09-30 08:42:38 +00:00
2016-09-17 21:00:14 +00:00
@click.command ( " request " , help = " Run processes for requesting certificates and configuring services " )
2016-01-14 22:47:30 +00:00
@click.option ( " -f " , " --fork " , default = False , is_flag = True , help = " Fork to background " )
2016-09-17 21:00:14 +00:00
def certidude_request ( fork ) :
if not os . path . exists ( const . CLIENT_CONFIG_PATH ) :
click . echo ( " No %s ! " % const . CLIENT_CONFIG_PATH )
return 1
if not os . path . exists ( const . SERVICES_CONFIG_PATH ) :
click . echo ( " No %s ! " % const . SERVICES_CONFIG_PATH )
return 1
2016-03-21 21:42:39 +00:00
clients = ConfigParser ( )
2016-09-17 21:00:14 +00:00
clients . readfp ( open ( const . CLIENT_CONFIG_PATH ) )
2016-01-14 22:47:30 +00:00
2016-09-17 21:00:14 +00:00
service_config = ConfigParser ( )
service_config . readfp ( open ( const . SERVICES_CONFIG_PATH ) )
2016-01-14 22:47:30 +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 )
2016-09-17 21:00:14 +00:00
for authority in clients . sections ( ) :
2017-01-10 13:01:16 +00:00
try :
endpoint_insecure = clients . getboolean ( authority , " insecure " )
except NoOptionError :
endpoint_insecure = False
2016-09-17 21:00:14 +00:00
try :
endpoint_common_name = clients . get ( authority , " common name " )
except NoOptionError :
endpoint_common_name = const . HOSTNAME
try :
endpoint_key_path = clients . get ( authority , " key path " )
except NoOptionError :
endpoint_key_path = " /var/lib/certidude/ %s /keys/ %s .pem " % ( authority , const . HOSTNAME )
try :
endpoint_request_path = clients . get ( authority , " request path " )
except NoOptionError :
endpoint_request_path = " /var/lib/certidude/ %s /requests/ %s .pem " % ( authority , const . HOSTNAME )
try :
endpoint_certificate_path = clients . get ( authority , " certificate path " )
except NoOptionError :
endpoint_certificate_path = " /var/lib/certidude/ %s /signed/ %s .pem " % ( authority , const . HOSTNAME )
try :
endpoint_authority_path = clients . get ( authority , " authority path " )
except NoOptionError :
endpoint_authority_path = " /var/lib/certidude/ %s /ca_crt.pem " % authority
try :
endpoint_revocations_path = clients . get ( authority , " revocations path " )
except NoOptionError :
endpoint_revocations_path = " /var/lib/certidude/ %s /ca_crl.pem " % authority
# TODO: Create directories automatically
extended_key_usage_flags = [ ]
try :
endpoint_key_flags = set ( [ j . strip ( ) for j in clients . get ( authority , " extended key usage flags " ) . lower ( ) . split ( " , " ) if j . strip ( ) ] )
except NoOptionError :
pass
else :
if " server auth " in endpoint_key_flags :
endpoint_key_flags - = set ( [ " server auth " ] )
extended_key_usage_flags . append ( ExtendedKeyUsageOID . SERVER_AUTH )
if " ike intermediate " in endpoint_key_flags :
endpoint_key_flags - = set ( [ " ike intermediate " ] )
extended_key_usage_flags . append ( x509 . ObjectIdentifier ( " 1.3.6.1.5.5.8.2.2 " ) )
if endpoint_key_flags :
raise ValueError ( " Extended key usage flags %s not understood! " % endpoint_key_flags )
# TODO: IKE Intermediate
if clients . get ( authority , " trigger " ) == " domain joined " :
if not os . path . exists ( " /etc/krb5.keytab " ) :
continue
elif clients . get ( authority , " trigger " ) != " interface up " :
2016-01-14 22:47:30 +00:00
continue
2016-09-17 21:00:14 +00:00
pid_path = os . path . join ( run_dir , authority + " .pid " )
2016-01-14 22:47:30 +00:00
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 )
2016-03-21 21:42:39 +00:00
except EnvironmentError :
2016-01-14 22:47:30 +00:00
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
2016-09-17 21:00:14 +00:00
2016-01-14 22:47:30 +00:00
with open ( pid_path , " w " ) as fh :
fh . write ( " %d \n " % os . getpid ( ) )
retries = 30
2016-09-17 21:00:14 +00:00
2016-01-14 22:47:30 +00:00
while retries > 0 :
try :
certidude_request_certificate (
2016-09-17 21:00:14 +00:00
authority ,
endpoint_key_path ,
endpoint_request_path ,
endpoint_certificate_path ,
endpoint_authority_path ,
endpoint_revocations_path ,
endpoint_common_name ,
extended_key_usage_flags ,
2016-01-14 22:47:30 +00:00
None ,
2017-01-10 13:01:16 +00:00
insecure = endpoint_insecure ,
2016-01-14 22:47:30 +00:00
autosign = True ,
wait = True )
break
except requests . exceptions . Timeout :
retries - = 1
continue
2016-09-17 21:00:14 +00:00
for endpoint in service_config . sections ( ) :
if service_config . get ( endpoint , " authority " ) != authority :
2016-01-14 22:47:30 +00:00
continue
2016-09-17 21:00:14 +00:00
click . echo ( " Configuring ' %s ' " % endpoint )
2016-01-14 22:47:30 +00:00
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 ]
2016-03-27 20:38:14 +00:00
# Intranet HTTPS handled by PKCS#12 bundle generation,
# so it will not be implemented here
2016-09-17 21:00:14 +00:00
# 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 " )
click . echo ( " Starting OpenVPN... " )
os . system ( " service openvpn start " )
2016-01-14 22:47:30 +00:00
continue
2016-09-17 21:00:14 +00:00
# IPSec set up with initscripts
if service_config . get ( endpoint , " service " ) == " init/strongswan " :
2016-01-14 22:47:30 +00:00
from ipsecparse import loads
config = loads ( open ( ' /etc/ipsec.conf ' ) . read ( ) )
2016-09-17 21:00:14 +00:00
if config [ " conn " ] [ server ] [ " left " ] == " %d efaultroute " :
config [ " conn " ] [ server ] [ " auto " ] = " start " # This is client
else :
config [ " conn " ] [ server ] [ " auto " ] = " add " # This is server
2016-01-14 22:47:30 +00:00
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
2016-09-17 21:00:14 +00:00
# OpenVPN set up with NetworkManager
if service_config . get ( endpoint , " service " ) == " network-manager/openvpn " :
nm_config = ConfigParser ( )
nm_config . add_section ( " connection " )
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 " , " yes " )
nm_config . set ( " vpn " , " cert-pass-flags " , " 0 " )
nm_config . set ( " vpn " , " tap-dev " , " yes " )
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 " , endpoint_key_path )
nm_config . set ( " vpn " , " cert " , endpoint_certificate_path )
nm_config . set ( " vpn " , " ca " , endpoint_authority_path )
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 " )
2016-01-14 22:47:30 +00:00
2016-09-17 21:00:14 +00:00
# Prevent creation of files with liberal permissions
os . umask ( 0o177 )
2015-12-12 22:34:08 +00:00
2016-09-17 21:00:14 +00:00
# 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 )
continue
2016-01-10 17:51:54 +00:00
2015-07-26 20:34:46 +00:00
2016-09-17 21:00:14 +00:00
# IPSec set up with NetworkManager
if service_config . get ( endpoint , " service " ) == " network-manager/strongswan " :
client_config = ConfigParser ( )
nm_config . add_section ( " connection " )
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 " , endpoint_key_path )
nm_config . set ( " vpn " , " usercert " , endpoint_certificate_path )
nm_config . set ( " vpn " , " certificate " , endpoint_authority_path )
nm_config . add_section ( " ipv4 " )
nm_config . set ( " ipv4 " , " method " , " auto " )
2015-07-26 20:34:46 +00:00
2016-09-17 21:00:14 +00:00
# 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 )
2015-07-26 20:34:46 +00:00
2016-09-17 21:00:14 +00:00
# Prevent creation of files with liberal permissions
os . umask ( 0o177 )
2015-12-12 22:34:08 +00:00
2016-09-17 21:00:14 +00:00
# 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 )
continue
2015-12-12 22:34:08 +00:00
2016-09-17 21:00:14 +00:00
# TODO: Puppet, OpenLDAP, <insert awesomeness here>
2016-01-14 22:47:30 +00:00
2016-09-17 21:00:14 +00:00
os . unlink ( pid_path )
2016-01-14 22:47:30 +00:00
2015-07-26 20:34:46 +00:00
@click.command ( " client " , help = " Setup X.509 certificates for application " )
2016-03-27 20:38:14 +00:00
@click.argument ( " server " )
2016-09-17 21:00:14 +00:00
@click.option ( " --common-name " , " -cn " , default = const . HOSTNAME , help = " Common name, ' %s ' by default " % const . HOSTNAME )
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 ( " --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 " )
2016-09-17 21:00:14 +00:00
@click.option ( " --key-path " , " -k " , default = const . HOSTNAME + " .key " , help = " Key path, %s .key by default " % const . HOSTNAME )
@click.option ( " --request-path " , " -r " , default = const . HOSTNAME + " .csr " , help = " Request path, %s .csr by default " % const . HOSTNAME )
@click.option ( " --certificate-path " , " -c " , default = const . HOSTNAME + " .crt " , help = " Certificate path, %s .crt by default " % const . HOSTNAME )
2015-07-26 20:34:46 +00:00
@click.option ( " --authority-path " , " -a " , default = " ca.crt " , help = " Certificate authority certificate path, ca.crt by default " )
2016-03-27 20:38:14 +00:00
@click.option ( " --revocations-path " , " -crl " , default = " ca.crl " , help = " Certificate revocation list, ca.crl by default " )
2015-07-26 20:34:46 +00:00
def certidude_setup_client ( quiet , * * kwargs ) :
return certidude_request_certificate ( * * kwargs )
@click.command ( " server " , help = " Set up OpenVPN server " )
2016-09-17 21:00:14 +00:00
@click.argument ( " authority " )
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 " )
2016-03-27 20:38:14 +00:00
@click.option ( " --local " , " -l " , default = " 0.0.0.0 " , help = " OpenVPN listening address, defaults to all interfaces " )
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 " )
2016-09-17 21:00:14 +00:00
def certidude_setup_openvpn_server ( authority , config , subnet , route , email_address , org_unit , local , proto , port ) :
# TODO: Make dirs
2015-07-26 20:34:46 +00:00
# TODO: Intelligent way of getting last IP address in the subnet
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
2016-09-17 21:00:14 +00:00
# Create corresponding section in Certidude client configuration file
client_config = ConfigParser ( )
if os . path . exists ( const . CLIENT_CONFIG_PATH ) :
client_config . readfp ( 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 :
client_config . set ( authority , " trigger " , " interface up " )
client_config . set ( authority , " common name " , const . HOSTNAME )
client_config . set ( authority , " subject alternative name dns " , const . FQDN )
client_config . set ( authority , " extended key usage flags " , " server auth " )
client_config . set ( authority , " request path " , " /etc/openvpn/keys/ %s .csr " % const . HOSTNAME )
client_config . set ( authority , " key path " , " /etc/openvpn/keys/ %s .key " % const . HOSTNAME )
client_config . set ( authority , " certificate path " , " /etc/openvpn/keys/ %s .crt " % const . HOSTNAME )
client_config . set ( authority , " authority path " , " /etc/openvpn/keys/ca.crt " )
client_config . set ( authority , " revocations path " , " /etc/openvpn/keys/ca.crl " )
with open ( const . CLIENT_CONFIG_PATH + " .part " , ' wb ' ) as fh :
client_config . write ( fh )
os . rename ( const . CLIENT_CONFIG_PATH + " .part " , const . CLIENT_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( authority , const . CLIENT_CONFIG_PATH ) )
# Create corresponding section in /etc/certidude/services.conf
endpoint = " OpenVPN server %s of %s " % ( const . FQDN , authority )
service_config = ConfigParser ( )
if os . path . exists ( const . SERVICES_CONFIG_PATH ) :
service_config . readfp ( open ( const . SERVICES_CONFIG_PATH ) )
if service_config . has_section ( endpoint ) :
click . echo ( " Section ' %s ' already exists in %s , not reconfiguring " % ( endpoint , const . SERVICES_CONFIG_PATH ) )
else :
service_config . add_section ( endpoint )
service_config . set ( endpoint , " authority " , authority )
service_config . set ( endpoint , " service " , " init/openvpn " )
with open ( const . SERVICES_CONFIG_PATH + " .part " , ' wb ' ) as fh :
service_config . write ( fh )
os . rename ( const . SERVICES_CONFIG_PATH + " .part " , const . SERVICES_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( endpoint , const . SERVICES_CONFIG_PATH ) )
dhparam_path = " /etc/openvpn/keys/dhparam.pem "
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 )
2016-09-17 21:00:14 +00:00
config . write ( " mode server \n " )
config . write ( " tls-server \n " )
config . write ( " proto %s \n " % proto )
config . write ( " port %d \n " % port )
config . write ( " dev tap \n " )
config . write ( " local %s \n " % local )
config . write ( " key %s \n " % client_config . get ( authority , " key path " ) )
config . write ( " cert %s \n " % client_config . get ( authority , " certificate path " ) )
config . write ( " ca %s \n " % client_config . get ( authority , " authority path " ) )
config . write ( " dh %s \n " % dhparam_path )
config . write ( " comp-lzo \n " )
config . write ( " user nobody \n " )
config . write ( " group nogroup \n " )
config . write ( " persist-tun \n " )
config . write ( " persist-key \n " )
config . write ( " ifconfig-pool-persist /tmp/openvpn-leases.txt \n " )
config . write ( " ifconfig %s 255.255.255.0 \n " % subnet_first )
config . write ( " server-bridge %s 255.255.255.0 %s %s \n " % ( subnet_first , subnet_second , subnet_last ) )
config . write ( " #crl-verify %s \n " % client_config . get ( authority , " revocations path " ) )
2015-07-26 20:34:46 +00:00
click . echo ( " Generated %s " % config . name )
2016-09-17 21:00:14 +00:00
click . echo ( " Inspect generated files and issue following to request certificate: " )
2015-07-26 20:34:46 +00:00
click . echo ( )
2016-09-17 21:00:14 +00:00
click . echo ( " certidude request " )
2015-07-26 20:34:46 +00:00
click . echo ( )
2016-09-17 21:00:14 +00:00
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: " )
2015-07-26 20:34:46 +00:00
click . echo ( )
2016-09-17 21:00:14 +00:00
click . echo ( " certidude sign %s " % const . HOSTNAME )
2015-07-26 20:34:46 +00:00
2016-03-21 21:42:39 +00:00
@click.command ( " nginx " , help = " Set up nginx as HTTPS server " )
2016-03-27 20:38:14 +00:00
@click.argument ( " server " )
2016-09-17 21:00:14 +00:00
@click.option ( " --common-name " , " -cn " , default = const . FQDN , help = " Common name, %s by default " % const . FQDN )
2016-03-21 21:42:39 +00:00
@click.option ( " --org-unit " , " -ou " , help = " Organizational unit " )
@click.option ( " --tls-config " ,
default = " /etc/nginx/conf.d/tls.conf " ,
type = click . File ( mode = " w " , atomic = True , lazy = True ) ,
help = " TLS configuration file of nginx, /etc/nginx/conf.d/tls.conf by default " )
@click.option ( " --site-config " , " -o " ,
2016-09-17 21:00:14 +00:00
default = " /etc/nginx/sites-available/ %s .conf " % const . HOSTNAME ,
2016-03-21 21:42:39 +00:00
type = click . File ( mode = " w " , atomic = True , lazy = True ) ,
2016-09-17 21:00:14 +00:00
help = " Site configuration file of nginx, /etc/nginx/sites-available/ %s .conf by default " % const . HOSTNAME )
2016-03-21 21:42:39 +00:00
@click.option ( " --directory " , " -d " , default = " /etc/nginx/ssl " , help = " Directory for keys, /etc/nginx/ssl by default " )
2016-09-17 21:00:14 +00:00
@click.option ( " --key-path " , " -key " , default = const . HOSTNAME + " .key " , help = " Key path, %s .key relative to -d by default " % const . HOSTNAME )
@click.option ( " --request-path " , " -csr " , default = const . HOSTNAME + " .csr " , help = " Request path, %s .csr relative to -d by default " % const . HOSTNAME )
@click.option ( " --certificate-path " , " -crt " , default = const . HOSTNAME + " .crt " , help = " Certificate path, %s .crt relative to -d by default " % const . HOSTNAME )
2016-03-21 21:42:39 +00:00
@click.option ( " --dhparam-path " , " -dh " , default = " dhparam2048.pem " , help = " Diffie/Hellman parameters path, dhparam2048.pem relative to -d by default " )
@click.option ( " --authority-path " , " -ca " , default = " ca.crt " , help = " Certificate authority certificate path, ca.crt relative to -d by default " )
2016-03-27 20:38:14 +00:00
@click.option ( " --revocations-path " , " -crl " , default = " ca.crl " , help = " Certificate revocation list, ca.crl relative to -d by default " )
2016-03-21 21:42:39 +00:00
@click.option ( " --verify-client " , " -vc " , type = click . Choice ( [ ' optional ' , ' on ' , ' off ' ] ) )
@expand_paths ( )
2016-09-17 21:00:14 +00:00
def certidude_setup_nginx ( authority , site_config , tls_config , common_name , org_unit , directory , key_path , request_path , certificate_path , authority_path , revocations_path , dhparam_path , verify_client ) :
2016-03-21 21:42:39 +00:00
# TODO: Intelligent way of getting last IP address in the subnet
if not os . path . exists ( certificate_path ) :
click . echo ( " As HTTPS 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 )
click . echo ( )
2016-09-17 21:00:14 +00:00
retval = certidude_request_certificate ( authority , key_path , request_path ,
2016-03-27 20:38:14 +00:00
certificate_path , authority_path , revocations_path , common_name , org_unit ,
2016-09-17 21:00:14 +00:00
extended_key_usage_flags = [ ExtendedKeyUsageOID . SERVER_AUTH ] ,
dns = const . FQDN , wait = True , bundle = True )
2016-03-21 21:42:39 +00:00
if not os . path . exists ( dhparam_path ) :
cmd = " openssl " , " dhparam " , " -out " , dhparam_path , " 2048 "
subprocess . check_call ( cmd )
if retval :
return retval
2016-09-17 21:00:14 +00:00
context = globals ( ) # Grab const.BLAH
2016-03-21 21:42:39 +00:00
context . update ( locals ( ) )
2016-09-17 21:00:14 +00:00
if os . path . exists ( site_client_config . name ) :
click . echo ( " Configuration file %s already exists, not overwriting " % site_client_config . name )
2016-03-21 21:42:39 +00:00
else :
2016-09-17 21:00:14 +00:00
site_client_config . write ( env . get_template ( " nginx-https-site.conf " ) . render ( context ) )
click . echo ( " Generated %s " % site_client_config . name )
2016-03-21 21:42:39 +00:00
2016-09-17 21:00:14 +00:00
if os . path . exists ( tls_client_config . name ) :
click . echo ( " Configuration file %s already exists, not overwriting " % tls_client_config . name )
2016-03-21 21:42:39 +00:00
else :
2016-09-17 21:00:14 +00:00
tls_client_config . write ( env . get_template ( " nginx-tls.conf " ) . render ( context ) )
click . echo ( " Generated %s " % tls_client_config . name )
2016-03-21 21:42:39 +00:00
click . echo ( )
click . echo ( " Inspect configuration files, enable it and start nginx service: " )
click . echo ( )
click . echo ( " ln -s %s /etc/nginx/sites-enabled/ %s " % (
2016-09-17 21:00:14 +00:00
os . path . relpath ( site_client_config . name , " /etc/nginx/sites-enabled " ) ,
os . path . basename ( site_client_config . name ) ) )
2016-03-21 21:42:39 +00:00
click . secho ( " service nginx restart " , bold = True )
click . echo ( )
2015-07-26 20:34:46 +00:00
@click.command ( " client " , help = " Set up OpenVPN client " )
2016-09-17 21:00:14 +00:00
@click.argument ( " authority " )
2015-07-26 20:34:46 +00:00
@click.argument ( " remote " )
@click.option ( ' --proto ' , " -t " , default = " udp " , type = click . Choice ( [ ' udp ' , ' tcp ' ] ) , help = " OpenVPN transport protocol, UDP by default " )
@click.option ( " --org-unit " , " -ou " , help = " Organizational unit " )
@click.option ( " --config " , " -o " ,
default = " /etc/openvpn/client-to-site.conf " ,
type = click . File ( mode = " w " , atomic = True , lazy = True ) ,
help = " OpenVPN configuration file " )
2016-09-17 21:00:14 +00:00
def certidude_setup_openvpn_client ( authority , remote , config , org_unit , proto ) :
# Create corresponding section in Certidude client configuration file
client_config = ConfigParser ( )
if os . path . exists ( const . CLIENT_CONFIG_PATH ) :
client_config . readfp ( 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 :
client_config . add_section ( authority )
client_config . set ( authority , " trigger " , " interface up " )
client_config . set ( authority , " common name " , const . HOSTNAME )
client_config . set ( authority , " request path " , " /etc/openvpn/keys/ %s .csr " % const . HOSTNAME )
client_config . set ( authority , " key path " , " /etc/openvpn/keys/ %s .key " % const . HOSTNAME )
client_config . set ( authority , " certificate path " , " /etc/openvpn/keys/ %s .crt " % const . HOSTNAME )
client_config . set ( authority , " authority path " , " /etc/openvpn/keys/ca.crt " )
client_config . set ( authority , " revocations path " , " /etc/openvpn/keys/ca.crl " )
with open ( const . CLIENT_CONFIG_PATH + " .part " , ' wb ' ) as fh :
client_config . write ( fh )
os . rename ( const . CLIENT_CONFIG_PATH + " .part " , const . CLIENT_CONFIG_PATH )
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
service_config = ConfigParser ( )
if os . path . exists ( const . SERVICES_CONFIG_PATH ) :
service_config . readfp ( open ( const . SERVICES_CONFIG_PATH ) )
if service_config . has_section ( endpoint ) :
click . echo ( " Section ' %s ' already exists in %s , not reconfiguring " % ( endpoint , const . SERVICES_CONFIG_PATH ) )
else :
service_config . add_section ( endpoint )
service_config . set ( endpoint , " authority " , authority )
service_config . set ( endpoint , " service " , " init/openvpn " )
with open ( const . SERVICES_CONFIG_PATH + " .part " , ' wb ' ) as fh :
service_config . write ( fh )
os . rename ( const . SERVICES_CONFIG_PATH + " .part " , const . SERVICES_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( endpoint , const . SERVICES_CONFIG_PATH ) )
config . write ( " client \n " )
config . write ( " remote %s \n " % remote )
config . write ( " remote-cert-tls server \n " )
config . write ( " proto %s \n " % proto )
config . write ( " dev tap \n " )
config . write ( " nobind \n " )
config . write ( " key %s \n " % client_config . get ( authority , " key path " ) )
config . write ( " cert %s \n " % client_config . get ( authority , " certificate path " ) )
config . write ( " ca %s \n " % client_config . get ( authority , " authority path " ) )
config . write ( " crl-verify %s \n " % client_config . get ( authority , " revocations path " ) )
config . write ( " comp-lzo \n " )
config . write ( " user nobody \n " )
config . write ( " group nogroup \n " )
config . write ( " persist-tun \n " )
config . write ( " persist-key \n " )
2015-07-26 20:34:46 +00:00
click . echo ( " Generated %s " % config . name )
2016-09-17 21:00:14 +00:00
click . echo ( " Inspect generated files and issue following to request certificate: " )
2015-07-26 20:34:46 +00:00
click . echo ( )
2016-09-17 21:00:14 +00:00
click . echo ( " certidude request " )
2015-07-26 20:34:46 +00:00
click . echo ( )
2016-09-17 21:00:14 +00:00
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: " )
2015-07-26 20:34:46 +00:00
click . echo ( )
2016-09-17 21:00:14 +00:00
click . echo ( " certidude sign %s " % const . HOSTNAME )
2015-07-26 20:34:46 +00:00
2015-08-13 08:11:08 +00:00
@click.command ( " server " , help = " Set up strongSwan server " )
2016-03-27 20:38:14 +00:00
@click.argument ( " server " )
2015-08-13 08:11:08 +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 )
2016-03-21 21:42:39 +00:00
@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 ( " --local " , " -l " , 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 " )
2016-09-17 21:00:14 +00:00
def certidude_setup_strongswan_server ( authority , config , secrets , subnet , route , email_address , org_unit , local , fqdn ) :
2015-12-16 17:41:49 +00:00
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
2016-09-17 21:00:14 +00:00
# Create corresponding section in Certidude client configuration file
client_config = ConfigParser ( )
if os . path . exists ( const . CLIENT_CONFIG_PATH ) :
client_config . readfp ( open ( const . CLIENT_CONFIG_PATH ) )
if client_config . has_section ( server ) :
click . echo ( " Section ' %s ' already exists in %s , remove to regenerate " % ( authority , const . CLIENT_CONFIG_PATH ) )
else :
client_config . set ( authority , " trigger " , " interface up " )
client_config . set ( authority , " common name " , const . FQDN )
client_config . set ( authority , " subject alternative name dns " , const . FQDN )
client_config . set ( authority , " extended key usage flags " , " server auth, ike intermediate " )
client_config . set ( authority , " request path " , " /etc/ipsec.d/reqs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " key path " , " /etc/ipsec.d/private/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " certificate path " , " /etc/ipsec.d/certs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/cacerts/ca.pem " )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/crls/ca.pem " )
with open ( const . CLIENT_CONFIG_PATH + " .part " , ' wb ' ) as fh :
client_config . write ( fh )
os . rename ( const . CLIENT_CONFIG_PATH + " .part " , const . CLIENT_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( authority , const . CLIENT_CONFIG_PATH ) )
# Create corresponding section to /etc/ipsec.conf
from ipsecparse import loads
config = loads ( open ( ' /etc/ipsec.conf ' ) . read ( ) )
config [ " conn " , server ] = dict (
leftsourceip = " %c onfig " ,
left = common_name ,
leftcert = certificate_path ,
leftsubnet = route . join ( " , " ) ,
right = " %a ny " ,
rightsourceip = subnet ,
keyexchange = " ikev2 " ,
keyingtries = " 300 " ,
dpdaction = dpdaction ,
closeaction = " restart " ,
auto = " ignore " )
with open ( " /etc/ipsec.conf.part " , " w " ) as fh :
fh . write ( client_config . dumps ( ) )
os . rename ( " /etc/ipsec.conf.part " , " /etc/ipsec.conf " )
2015-08-13 08:11:08 +00:00
click . echo ( )
2016-03-29 15:37:28 +00:00
click . echo ( " If you ' re running Ubuntu make sure you ' re not affected by #1505222 " )
click . echo ( " https://bugs.launchpad.net/ubuntu/+source/strongswan/+bug/1505222 " )
2015-08-13 08:11:08 +00:00
@click.command ( " client " , help = " Set up strongSwan client " )
2016-03-27 20:38:14 +00:00
@click.argument ( " server " )
2015-08-13 08:11:08 +00:00
@click.argument ( " remote " )
@click.option ( " --org-unit " , " -ou " , help = " Organizational unit " )
2016-09-17 21:00:14 +00:00
def certidude_setup_strongswan_client ( authority , config , org_unit , remote , dpdaction ) :
# Create corresponding section in /etc/certidude/client.conf
client_config = ConfigParser ( )
if os . path . exists ( const . CLIENT_CONFIG_PATH ) :
client_config . readfp ( open ( const . CLIENT_CONFIG_PATH ) )
if client_config . has_section ( server ) :
click . echo ( " Section ' %s ' already exists in %s , remove to regenerate " % ( authority , const . CLIENT_CONFIG_PATH ) )
else :
client_config . add_section ( authority )
client_config . set ( authority , " trigger " , " interface up " )
client_config . set ( authority , " common name " , const . HOSTNAME )
client_config . set ( authority , " request path " , " /etc/ipsec.d/reqs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " key path " , " /etc/ipsec.d/private/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " certificate path " , " /etc/ipsec.d/certs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/cacerts/ca.pem " )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/crls/ca.pem " )
with open ( const . CLIENT_CONFIG_PATH + " .part " , ' wb ' ) as fh :
client_config . write ( fh )
os . rename ( const . CLIENT_CONFIG_PATH + " .part " , const . CLIENT_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( authority , const . CLIENT_CONFIG_PATH ) )
# Create corresponding section in /etc/ipsec.conf
from ipsecparse import loads
config = loads ( open ( ' /etc/ipsec.conf ' ) . read ( ) )
config [ " conn " , server ] = dict (
leftsourceip = " %c onfig " ,
left = " %d efaultroute " ,
leftcert = certificate_path ,
rightid = " %a ny " ,
right = remote ,
rightsubnet = route ,
keyexchange = " ikev2 " ,
keyingtries = " 300 " ,
dpdaction = " restart " ,
closeaction = " restart " ,
auto = " ignore " )
with open ( " /etc/ipsec.conf.part " , " w " ) as fh :
fh . write ( client_config . dumps ( ) )
os . rename ( " /etc/ipsec.conf.part " , " /etc/ipsec.conf " )
click . echo ( " Generated section %s in %s " % ( authority , client_config . name ) )
click . echo ( " Run ' certidude request ' to request certificates and to enable services " )
2015-08-13 08:11:08 +00:00
2015-10-17 15:07:26 +00:00
@click.command ( " networkmanager " , help = " Set up strongSwan client via NetworkManager " )
2016-03-27 20:38:14 +00:00
@click.argument ( " server " ) # Certidude server
@click.argument ( " remote " ) # StrongSwan gateway
2015-10-17 15:07:26 +00:00
@click.option ( " --org-unit " , " -ou " , help = " Organizational unit " )
2016-09-17 21:00:14 +00:00
def certidude_setup_strongswan_networkmanager ( server , remote , org_unit ) :
2016-03-27 20:38:14 +00:00
endpoint = " IPSec to %s " % remote
2015-10-17 15:07:26 +00:00
2016-09-17 21:00:14 +00:00
# Create corresponding section in /etc/certidude/client.conf
client_config = ConfigParser ( )
if os . path . exists ( const . CLIENT_CONFIG_PATH ) :
client_config . readfp ( open ( const . CLIENT_CONFIG_PATH ) )
if client_config . has_section ( server ) :
click . echo ( " Section ' %s ' already exists in %s , remove to regenerate " % ( authority , const . CLIENT_CONFIG_PATH ) )
2016-03-27 20:38:14 +00:00
else :
2016-09-17 21:00:14 +00:00
client_config . add_section ( authority )
client_config . set ( authority , " trigger " , " interface up " )
client_config . set ( authority , " common name " , const . HOSTNAME )
client_config . set ( authority , " org unit " , org_unit )
client_config . set ( authority , " request path " , " /etc/ipsec.d/reqs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " key path " , " /etc/ipsec.d/private/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " certificate path " , " /etc/ipsec.d/certs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/cacerts/ca.pem " )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/crls/ca.pem " )
with open ( const . CLIENT_CONFIG_PATH + " .part " , ' wb ' ) as fh :
client_config . write ( fh )
os . rename ( const . CLIENT_CONFIG_PATH + " .part " , const . CLIENT_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( authority , const . CLIENT_CONFIG_PATH ) )
# Create corresponding section in /etc/certidude/services.conf
service_config = ConfigParser ( )
if os . path . exists ( const . SERVICES_CONFIG_PATH ) :
service_config . readfp ( open ( const . SERVICES_CONFIG_PATH ) )
if service_config . has_section ( endpoint ) :
click . echo ( " Section ' %s ' already exists in %s , remove to regenerate " % ( endpoint , const . SERVICES_CONFIG_PATH ) )
else :
service_config . add_section ( endpoint )
service_config . set ( authority , " authority " , server )
service_config . set ( authority , " remote " , remote )
service_config . set ( authority , " service " , " network-manager/strongswan-client " )
with open ( const . SERVICES_CONFIG_PATH + " .part " , ' wb ' ) as fh :
service_config . write ( fh )
os . rename ( const . SERVICES_CONFIG_PATH + " .part " , const . SERVICES_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( endpoint , const . SERVICES_CONFIG_PATH ) )
2015-10-17 15:07:26 +00:00
2016-03-27 20:38:14 +00:00
@click.command ( " networkmanager " , help = " Set up OpenVPN client via NetworkManager " )
@click.argument ( " server " ) # Certidude server
@click.argument ( " remote " ) # OpenVPN gateway
2016-09-17 21:00:14 +00:00
@click.option ( " --common-name " , " -cn " , default = const . HOSTNAME , help = " Common name, %s by default " % const . HOSTNAME )
2016-03-27 20:38:14 +00:00
@click.option ( " --org-unit " , " -ou " , help = " Organizational unit " )
@click.option ( " --email-address " , " -m " , help = " E-mail associated with the request, none by default " )
2016-09-17 21:00:14 +00:00
def certidude_setup_openvpn_networkmanager ( authority , email_address , org_unit , remote ) :
# Create corresponding section in /etc/certidude/client.conf
client_config = ConfigParser ( )
if os . path . exists ( const . CLIENT_CONFIG_PATH ) :
client_config . readfp ( open ( const . CLIENT_CONFIG_PATH ) )
if client_config . has_section ( server ) :
click . echo ( " Section ' %s ' already exists in %s , remove to regenerate " % ( authority , const . CLIENT_CONFIG_PATH ) )
else :
client_config . add_section ( authority )
client_config . set ( authority , " trigger " , " interface up " )
client_config . set ( authority , " common name " , const . HOSTNAME )
client_config . set ( authority , " request path " , " /etc/ipsec.d/reqs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " key path " , " /etc/ipsec.d/private/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " certificate path " , " /etc/ipsec.d/certs/ %s .pem " % const . HOSTNAME )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/cacerts/ca.pem " )
client_config . set ( authority , " authority path " , " /etc/ipsec.d/crls/ca.pem " )
with open ( const . CLIENT_CONFIG_PATH + " .part " , ' wb ' ) as fh :
client_config . write ( fh )
os . rename ( const . CLIENT_CONFIG_PATH + " .part " , const . CLIENT_CONFIG_PATH )
click . echo ( " Section ' %s ' added to %s " % ( authority , const . CLIENT_CONFIG_PATH ) )
2015-10-17 15:07:26 +00:00
2016-03-27 20:38:14 +00:00
endpoint = " OpenVPN to %s " % remote
2015-10-17 15:07:26 +00:00
2016-09-17 21:00:14 +00:00
service_config = ConfigParser ( )
if os . path . exists ( const . SERVICES_CONFIG_PATH ) :
service_config . readfp ( open ( const . SERVICES_CONFIG_PATH ) )
if service_config . has_section ( endpoint ) :
click . echo ( " Section ' %s ' already exists in %s , remove to regenerate " % ( endpoint , const . SERVICES_CONFIG_PATH ) )
2016-03-27 20:38:14 +00:00
else :
2016-09-17 21:00:14 +00:00
service_config . add_section ( endpoint )
service_config . set ( authority , " authority " , server )
service_config . set ( endpoint , " remote " , remote )
service_config . set ( endpoint , " service " , " network-manager/openvpn " )
service_config . write ( open ( " /etc/certidude/services.conf " , " w " ) )
2016-03-27 20:38:14 +00:00
click . echo ( " Section %s added to /etc/certidude/client.conf " % endpoint )
2015-10-17 15:07:26 +00:00
2016-03-29 19:03:27 +00:00
@click.command ( " authority " , help = " Set up Certificate Authority in a directory " )
2015-08-13 08:11:08 +00:00
@click.option ( " --username " , default = " certidude " , help = " Service user account, created if necessary, ' certidude ' by default " )
2016-03-29 19:03:27 +00:00
@click.option ( " --static-path " , default = os . path . join ( os . path . dirname ( __file__ ) , " static " ) , help = " Path to Certidude ' s static JS/CSS/etc " )
@click.option ( " --kerberos-keytab " , default = " /etc/certidude/server.keytab " , help = " Kerberos keytab for using ' kerberos ' authentication backend, /etc/certidude/server.keytab by default " )
2015-08-13 08:11:08 +00:00
@click.option ( " --nginx-config " , " -n " ,
2016-03-29 19:03:27 +00:00
default = " /etc/nginx/sites-available/certidude.conf " ,
2015-08-13 08:11:08 +00:00
type = click . File ( mode = " w " , atomic = True , lazy = True ) ,
2016-03-29 19:03:27 +00:00
help = " nginx site config for serving Certidude, /etc/nginx/sites-available/certidude by default " )
@click.option ( " --parent " , " -p " , help = " Parent CA, none by default " )
2016-09-17 21:00:14 +00:00
@click.option ( " --common-name " , " -cn " , default = const . FQDN , help = " Common name, fully qualified hostname by default " )
2016-03-29 19:03:27 +00:00
@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 " )
@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 = 20 * 60 , help = " Revocation list lifetime in days, 1200 seconds (20 minutes) by default " )
@click.option ( " --organization " , " -o " , default = None , help = " Company or organization name " )
@click.option ( " --organizational-unit " , " -ou " , default = None )
@click.option ( " --revoked-url " , default = None , help = " CRL distribution URL " )
@click.option ( " --certificate-url " , default = None , help = " Authority certificate URL " )
2016-09-17 21:00:14 +00:00
@click.option ( " --push-server " , default = " http:// " + const . FQDN , help = " Push server, by default http:// %s " % const . FQDN )
@click.option ( " --email-address " , default = " certidude@ " + const . FQDN , help = " E-mail address of the CA " )
@click.option ( " --directory " , help = " Directory for authority files " )
2016-03-29 19:03:27 +00:00
@click.option ( " --server-flags " , is_flag = True , help = " Add TLS Server and IKE Intermediate extended key usage flags " )
2016-09-17 21:00:14 +00:00
@click.option ( " --outbox " , default = " smtp://smtp. %s " % const . DOMAIN , help = " SMTP server, smtp://smtp. %s by default " % const . DOMAIN )
def certidude_setup_authority ( username , static_path , kerberos_keytab , nginx_config , parent , country , state , locality , organization , organizational_unit , common_name , directory , certificate_lifetime , authority_lifetime , revocation_list_lifetime , revoked_url , certificate_url , push_server , email_address , outbox , server_flags ) :
if not directory :
if os . getuid ( ) :
directory = os . path . join ( os . path . expanduser ( " ~/.certidude " ) , const . FQDN )
else :
directory = os . path . join ( " /var/lib/certidude " , const . FQDN )
click . echo ( " Using fully qualified hostname: %s " % common_name )
2016-03-29 19:03:27 +00:00
# Expand variables
2016-03-30 19:01:02 +00:00
if not revoked_url :
revoked_url = " http:// %s /api/revoked/ " % common_name
if not certificate_url :
certificate_url = " http:// %s /api/certificate/ " % common_name
2016-03-29 19:03:27 +00:00
ca_key = os . path . join ( directory , " ca_key.pem " )
ca_crt = os . path . join ( directory , " ca_crt.pem " )
2016-09-17 21:00:14 +00:00
2016-03-29 19:03:27 +00:00
if not static_path . endswith ( " / " ) :
static_path + = " / "
2016-03-21 21:42:39 +00:00
2016-09-17 21:00:14 +00:00
if os . getuid ( ) == 0 :
try :
pwd . getpwnam ( " certidude " )
except KeyError :
cmd = " adduser " , " --system " , " --no-create-home " , " --group " , " certidude "
if subprocess . call ( cmd ) :
click . echo ( " Failed to create system user ' certidude ' " )
return 255
if os . path . exists ( kerberos_keytab ) :
click . echo ( " Service principal keytab found in ' %s ' " % kerberos_keytab )
else :
click . echo ( " To use ' kerberos ' authentication backend join the domain and create service principal with: " )
click . echo ( )
click . echo ( " KRB5_KTNAME=FILE: %s net ads keytab add HTTP -P " % kerberos_keytab )
click . echo ( " chown %s %s " % ( username , kerberos_keytab ) )
click . echo ( )
2015-08-13 08:11:08 +00:00
2016-09-17 21:00:14 +00:00
if os . path . exists ( " /etc/krb5.keytab " ) and os . path . exists ( " /etc/samba/smb.conf " ) :
# Fetch Kerberos ticket for system account
cp = ConfigParser ( )
cp . read ( " /etc/samba/smb.conf " )
realm = cp . get ( " global " , " realm " )
domain = realm . lower ( )
name = cp . get ( " global " , " netbios name " )
base = " , " . join ( [ " dc= " + j for j in domain . split ( " . " ) ] )
with open ( " /etc/cron.hourly/certidude " , " w " ) as fh :
fh . write ( env . get_template ( " ldap-ticket-renewal.sh " ) . 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 " )
else :
click . echo ( " Warning: /etc/krb5.keytab or /etc/samba/smb.conf not found, Kerberos unconfigured " )
2015-08-13 08:11:08 +00:00
2016-03-29 19:03:27 +00:00
2016-09-17 21:00:14 +00:00
working_directory = os . path . realpath ( os . path . dirname ( __file__ ) )
certidude_path = sys . argv [ 0 ]
2015-08-13 08:11:08 +00:00
2016-09-17 21:00:14 +00:00
if not os . path . exists ( " /etc/nginx " ) :
click . echo ( " Directory /etc/nginx does not exist, hence not creating nginx configuration " )
listen = " 0.0.0.0 "
port = " 80 "
else :
nginx_client_config . write ( env . get_template ( " nginx.conf " ) . render ( vars ( ) ) )
click . echo ( " Generated: %s " % nginx_client_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 " )
click . echo ( " Symlinked %s -> /etc/nginx/sites-enabled/ " % nginx_client_config . name )
if os . path . exists ( " /etc/nginx/sites-enabled/default " ) :
os . unlink ( " /etc/nginx/sites-enabled/default " )
if not push_server :
click . echo ( " Remember to install nchan instead of regular nginx! " )
if os . path . exists ( " /etc/systemd " ) :
if os . path . exists ( " /etc/systemd/system/certidude.service " ) :
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 ( ) ) )
click . echo ( " File /etc/systemd/system/certidude.service created " )
else :
NotImplemented # No systemd
2015-08-13 08:11:08 +00:00
2016-09-17 21:00:14 +00:00
_ , _ , uid , gid , gecos , root , shell = pwd . getpwnam ( " certidude " )
os . setgid ( gid )
else :
click . echo ( " Not root, skipping user and system config creation " )
2015-10-26 20:50:10 +00:00
2016-09-17 21:00:14 +00:00
if not os . path . exists ( const . CONFIG_DIR ) :
click . echo ( " Creating %s " % const . CONFIG_DIR )
os . makedirs ( const . CONFIG_DIR )
2015-09-29 12:04:35 +00:00
2016-09-17 21:00:14 +00:00
if os . path . exists ( const . CONFIG_PATH ) :
click . echo ( " Configuration file %s already exists, remove to regenerate " % const . CONFIG_PATH )
2016-03-29 19:03:27 +00:00
else :
os . umask ( 0o137 )
2016-09-17 21:00:14 +00:00
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 ( ) ) )
click . echo ( " Generated %s " % const . CONFIG_PATH )
2016-03-29 19:03:27 +00:00
if os . path . lexists ( directory ) :
2016-09-17 21:00:14 +00:00
click . echo ( " CA directory %s already exists, remove to regenerate " % directory )
else :
click . echo ( " CA configuration files are saved to: {} " . format ( directory ) )
2015-07-26 20:34:46 +00:00
2016-09-17 21:00:14 +00:00
click . echo ( " Generating 4096-bit RSA key... " )
2015-07-26 20:34:46 +00:00
2016-09-17 21:00:14 +00:00
key = rsa . generate_private_key (
public_exponent = 65537 ,
key_size = 4096 ,
backend = default_backend ( )
)
2016-01-10 17:51:54 +00:00
2016-09-17 21:00:14 +00:00
subject = issuer = x509 . Name ( [
x509 . NameAttribute ( o , value ) for o , value in (
( NameOID . COUNTRY_NAME , country ) ,
( NameOID . STATE_OR_PROVINCE_NAME , state ) ,
( NameOID . LOCALITY_NAME , locality ) ,
( NameOID . ORGANIZATION_NAME , organization ) ,
( NameOID . COMMON_NAME , common_name ) ,
) if value
] )
builder = x509 . CertificateBuilder (
) . subject_name ( subject
) . issuer_name ( issuer
) . public_key ( key . public_key ( )
) . not_valid_before ( datetime . utcnow ( )
) . not_valid_after (
datetime . utcnow ( ) + timedelta ( days = authority_lifetime )
) . serial_number ( 1
) . add_extension ( x509 . BasicConstraints ( ca = True , path_length = None ) , critical = True ,
) . add_extension ( x509 . KeyUsage (
digital_signature = True ,
key_encipherment = False ,
content_commitment = False ,
data_encipherment = False ,
key_agreement = False ,
key_cert_sign = True ,
crl_sign = True ,
encipher_only = False ,
decipher_only = False ) , critical = True ,
) . add_extension (
x509 . SubjectAlternativeName ( [
x509 . DNSName ( common_name ) ,
x509 . RFC822Name ( email_address )
] ) ,
critical = False ,
) . add_extension (
x509 . SubjectKeyIdentifier . from_public_key ( key . public_key ( ) ) ,
critical = False
) . add_extension (
x509 . AuthorityKeyIdentifier . from_issuer_public_key ( key . public_key ( ) ) ,
critical = False
)
if server_flags :
builder = builder . add_extension ( x509 . ExtendedKeyUsage ( [
ExtendedKeyUsageOID . SERVER_AUTH ,
x509 . ObjectIdentifier ( " 1.3.6.1.5.5.8.2.2 " ) ] ) , critical = False )
cert = builder . sign ( key , hashes . SHA512 ( ) , default_backend ( ) )
click . echo ( " Signing %s ... " % cert . subject )
# Create authority directory with 750 permissions
os . umask ( 0o027 )
if not os . path . exists ( directory ) :
os . makedirs ( directory )
# Create subdirectories with 770 permissions
os . umask ( 0o007 )
for subdir in ( " signed " , " requests " , " revoked " , " expired " ) :
if not os . path . exists ( os . path . join ( directory , subdir ) ) :
os . mkdir ( os . path . join ( directory , subdir ) )
2015-07-26 20:34:46 +00:00
2016-09-17 21:00:14 +00:00
# Set permission bits to 640
os . umask ( 0o137 )
with open ( ca_crt , " wb " ) as fh :
fh . write ( cert . public_bytes ( serialization . Encoding . PEM ) )
# Set permission bits to 600
os . umask ( 0o177 )
with open ( ca_key , " wb " ) as fh :
fh . write ( key . private_bytes (
encoding = serialization . Encoding . PEM ,
format = serialization . PrivateFormat . TraditionalOpenSSL ,
encryption_algorithm = serialization . NoEncryption ( ) # TODO: Implement passphrase
) )
2015-07-26 20:34:46 +00:00
2015-07-12 19:22:10 +00:00
click . echo ( )
click . echo ( " Use following commands to inspect the newly created files: " )
click . echo ( )
2015-11-15 14:55:26 +00:00
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 ( )
2015-07-12 19:22:10 +00:00
click . echo ( " Use following command to serve CA read-only: " )
click . echo ( )
click . echo ( " certidude serve " )
2015-07-26 20:34:46 +00:00
2016-03-31 21:01:58 +00:00
@click.command ( " users " , help = " List users " )
def certidude_users ( ) :
from certidude . user import User
admins = set ( User . objects . filter_admins ( ) )
for user in User . objects . all ( ) :
print " %s ; %s ; %s ; %s ; %s " % (
" admin " if user in admins else " user " ,
user . name , user . given_name , user . surname , user . mail )
2016-09-17 21:00:14 +00:00
2016-03-31 21:01:58 +00:00
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 " )
2015-12-12 22:34:08 +00:00
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
2015-12-16 17:41:49 +00:00
from certidude import authority
2015-07-26 20:34:46 +00:00
from pycountry import countries
2016-09-17 21:00:14 +00:00
2015-07-26 20:34:46 +00:00
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
2015-12-12 22:34:08 +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 :
2015-12-12 22:34:08 +00:00
click . echo ( " v " + j . path + " " + j . identity )
2015-10-28 08:51:52 +00:00
elif NOW > j . expires :
2015-12-12 22:34:08 +00:00
click . echo ( " e " + j . path + " " + j . identity )
2015-10-28 08:51:52 +00:00
else :
2015-12-12 22:34:08 +00:00
click . echo ( " y " + j . path + " " + j . identity )
continue
2015-07-12 19:22:10 +00:00
2015-12-12 22:34:08 +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
2015-12-12 22:34:08 +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
2015-12-12 22:34:08 +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 ) :
2016-09-17 21:00:14 +00:00
from certidude import authority , config
2015-12-12 22:34:08 +00:00
request = authority . get_request ( common_name )
2016-09-17 21:00:14 +00:00
# Use signer if this is regular client CSR
2015-12-12 22:34:08 +00:00
if request . signable :
# Sign via signer process
cert = authority . sign ( request )
2016-09-17 21:00:14 +00:00
# Sign directly if it's eg. TLS server CSR
2015-12-12 22:34:08 +00:00
else :
2016-09-17 21:00:14 +00:00
# Load CA private key and certificate
private_key = serialization . load_pem_private_key (
open ( config . AUTHORITY_PRIVATE_KEY_PATH ) . read ( ) ,
password = None , # TODO: Ask password for private key?
backend = default_backend ( ) )
authority_certificate = x509 . load_pem_x509_certificate (
open ( config . AUTHORITY_CERTIFICATE_PATH ) . read ( ) ,
backend = default_backend ( ) )
# Drop privileges
# to use LDAP service ticket to read usernames of the admins group
# in order to send e-mail
_ , _ , uid , gid , gecos , root , shell = pwd . getpwnam ( " certidude " )
os . setgroups ( [ ] )
os . setgid ( gid )
os . setuid ( uid )
2015-08-13 08:11:08 +00:00
2016-09-17 21:00:14 +00:00
# Sign directly using private key
cert = authority . sign2 ( request , private_key , authority_certificate ,
overwrite , True , lifetime )
2015-07-26 20:34:46 +00:00
2016-03-21 21:42:39 +00:00
2016-09-17 21:00:14 +00:00
@click.command ( " serve " , help = " Run server " )
@click.option ( " -p " , " --port " , default = 8080 if os . getuid ( ) else 80 , help = " Listen port " )
2015-07-12 19:22:10 +00:00
@click.option ( " -l " , " --listen " , default = " 0.0.0.0 " , help = " Listen address " )
2016-09-17 21:00:14 +00:00
def certidude_serve ( port , listen ) :
from certidude . signer import SignServer
from certidude import const
click . echo ( " Using configuration from: %s " % const . CONFIG_PATH )
2016-03-21 21:42:39 +00:00
from certidude import config
2016-09-17 21:00:14 +00:00
# Fetch UID, GID of certidude user
import pwd
_ , _ , uid , gid , gecos , root , shell = pwd . getpwnam ( " certidude " )
restricted_groups = [ ]
restricted_groups . append ( gid )
"""
Spawn signer process
"""
child_pid = os . fork ( )
if child_pid :
pass
else :
click . echo ( " Signer process spawned with PID %d at %s " % ( os . getpid ( ) , const . SIGNER_SOCKET_PATH ) )
setproctitle ( " [signer] " )
with open ( const . SIGNER_PID_PATH , " w " ) as fh :
fh . write ( " %d \n " % os . getpid ( ) )
logging . basicConfig (
filename = const . SIGNER_LOG_PATH ,
level = logging . INFO )
os . umask ( 0o007 )
server = SignServer ( )
# Drop privileges
if not os . getuid ( ) :
os . chown ( const . SIGNER_SOCKET_PATH , uid , gid )
os . chmod ( const . SIGNER_SOCKET_PATH , 0770 )
click . echo ( " Dropping privileges of signer " )
_ , _ , uid , gid , gecos , root , shell = pwd . getpwnam ( " nobody " )
os . setgroups ( [ ] )
os . setgid ( gid )
os . setuid ( uid )
else :
click . echo ( " Not dropping privileges of signer process " )
asyncore . loop ( )
return
2016-03-21 21:42:39 +00:00
click . echo ( " Users subnets: %s " %
" , " . join ( [ str ( j ) for j in config . USER_SUBNETS ] ) )
click . echo ( " Administrative subnets: %s " %
" , " . join ( [ str ( j ) for j in config . ADMIN_SUBNETS ] ) )
click . echo ( " Auto-sign enabled for following subnets: %s " %
" , " . join ( [ str ( j ) for j in config . AUTOSIGN_SUBNETS ] ) )
click . echo ( " Request submissions allowed from following subnets: %s " %
" , " . join ( [ str ( j ) for j in config . REQUEST_SUBNETS ] ) )
2015-07-26 20:34:46 +00:00
logging . basicConfig (
2016-09-17 21:00:14 +00:00
filename = const . SERVER_LOG_PATH ,
2015-07-26 20:34:46 +00:00
level = logging . DEBUG )
2015-07-12 19:22:10 +00:00
click . echo ( " Serving API at %s : %d " % ( listen , port ) )
from wsgiref . simple_server import make_server , WSGIServer
2016-09-17 21:00:14 +00:00
from SocketServer import ThreadingMixIn , ForkingMixIn
2015-09-02 05:35:18 +00:00
from certidude . api import certidude_app , StaticResource
2015-07-12 19:22:10 +00:00
2016-09-17 21:00:14 +00:00
class ThreadingWSGIServer ( ForkingMixIn , 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
2015-09-01 11:02:00 +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-09-02 05:35:18 +00:00
2015-07-12 19:22:10 +00:00
httpd = make_server ( listen , port , app , ThreadingWSGIServer )
2015-09-03 09:00:45 +00:00
2016-09-17 21:00:14 +00:00
"""
Drop privileges
"""
if os . getuid ( ) == 0 :
# Initialize LDAP service ticket
if os . path . exists ( " /etc/cron.hourly/certidude " ) :
os . system ( " /etc/cron.hourly/certidude " )
2016-03-21 21:42:39 +00:00
2016-09-17 21:00:14 +00:00
# Drop privileges
2016-03-21 21:42:39 +00:00
if config . AUTHENTICATION_BACKENDS == { " pam " } :
2016-02-29 21:06:42 +00:00
# PAM needs access to /etc/shadow
import grp
2016-09-17 21:00:14 +00:00
name , passwd , num , mem = grp . getgrnam ( " shadow " )
2016-02-29 21:06:42 +00:00
click . echo ( " Adding current user to shadow group due to PAM authentication backend " )
2016-09-17 21:00:14 +00:00
restricted_groups . append ( num )
2016-02-29 21:06:42 +00:00
2016-03-21 21:42:39 +00:00
os . setgroups ( restricted_groups )
2015-07-12 19:22:10 +00:00
os . setgid ( gid )
os . setuid ( uid )
2016-02-29 21:06:42 +00:00
click . echo ( " Switched to user %s (uid= %d , gid= %d ); member of groups %s " %
2016-09-17 21:00:14 +00:00
( " certidude " , os . getuid ( ) , os . getgid ( ) , " , " . join ( [ str ( j ) for j in os . getgroups ( ) ] ) ) )
2016-02-29 21:06:42 +00:00
2015-07-26 20:34:46 +00:00
os . umask ( 0o007 )
2016-09-17 21:00:14 +00:00
# Set up log handlers
log_handlers = [ ]
if config . LOGGING_BACKEND == " sql " :
from certidude . mysqllog import LogHandler
from certidude . api . log import LogResource
uri = config . cp . get ( " logging " , " database " )
log_handlers . append ( LogHandler ( uri ) )
app . add_route ( " /api/log/ " , LogResource ( uri ) )
elif config . LOGGING_BACKEND == " syslog " :
from logging . handlers import SyslogHandler
log_handlers . append ( SysLogHandler ( ) )
# Browsing syslog via HTTP is obviously not possible out of the box
elif config . LOGGING_BACKEND :
raise ValueError ( " Invalid logging.backend = %s " % config . LOGGING_BACKEND )
if config . PUSH_PUBLISH :
from certidude . push import PushLogHandler
log_handlers . append ( PushLogHandler ( ) )
for facility in " api " , " cli " :
logger = logging . getLogger ( facility )
logger . setLevel ( logging . DEBUG )
for handler in log_handlers :
logger . addHandler ( handler )
import atexit
def exit_handler ( ) :
logging . getLogger ( " cli " ) . debug ( " Shutting down Certidude " )
atexit . register ( exit_handler )
logging . getLogger ( " cli " ) . debug ( " Started Certidude at %s " , const . FQDN )
print " Ready "
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
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 )
2015-10-17 15:07:26 +00:00
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 )
2016-03-27 20:38:14 +00:00
certidude_setup_openvpn . add_command ( certidude_setup_openvpn_networkmanager )
2015-07-26 20:34:46 +00:00
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 )
2016-03-21 21:42:39 +00:00
certidude_setup . add_command ( certidude_setup_nginx )
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 )
2016-03-31 21:01:58 +00:00
entry_point . add_command ( certidude_users )
2016-09-17 21:00:14 +00:00
if __name__ == " __main__ " :
entry_point ( )