Files

212 lines
5.4 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
import os, shutil
from subprocess import call
KEY_STRENGTH = 2048
DAYS_VALID = 3653 # ~10 years
TMPL_CA_CERT = "openssl req -x509 -config openssl.cnf -newkey rsa:{0} -days {1} -out cacert.pem -outform PEM -subj {2} -nodes"
TMPL_CONVERT = "openssl x509 -in cacert.pem -out cacert.cer -outform DER"
DEV_NULL = open('/dev/null', 'w')
OPENSSL_CNF = """
[ ca ]
default_ca = ca
[ ca ]
dir = .
certificate = $dir/cacert.pem
database = $dir/index.txt
new_certs_dir = $dir/certs
private_key = $dir/private/cakey.pem
serial = $dir/serial
default_crl_days = 7
default_days = 365
default_md = sha1
policy = ca_policy
x509_extensions = certificate_extensions
[ ca_policy ]
commonName = supplied
stateOrProvinceName = optional
countryName = optional
emailAddress = optional
organizationName = optional
organizationalUnitName = optional
[ certificate_extensions ]
basicConstraints = CA:false
[ req ]
default_bits = 2048
default_keyfile = ./private/cakey.pem
default_md = sha1
prompt = yes
distinguished_name = root_ca_distinguished_name
x509_extensions = root_ca_extensions
[ root_ca_distinguished_name ]
commonName = hostname
[ root_ca_extensions ]
basicConstraints = CA:true
keyUsage = keyCertSign, cRLSign
[ client_ca_extensions ]
basicConstraints = CA:false
keyUsage = digitalSignature
extendedKeyUsage = 1.3.6.1.5.5.7.3.2
[ server_ca_extensions ]
basicConstraints = CA:false
keyUsage = keyEncipherment
extendedKeyUsage = 1.3.6.1.5.5.7.3.1
"""
class CA:
def __init__(self, cadir, subj):
self.cadir = self.normalize_directory_path(cadir)
self.subj = subj
def normalize_directory_path(self, path):
if path.endswith(os.sep):
return path[:-1]
else:
return path
def create_dir_if_not_exist(self, dir):
if not os.path.exists(dir):
try:
os.mkdir(dir)
except Exception as e:
return dict(success=False, msg=e)
return dict(success=True)
def execute_command(self, cmd):
call(cmd, shell=True, stdout=DEV_NULL, stderr=DEV_NULL)
def validate_setup(self):
certDirValid = self.create_dir_if_not_exist(self.cadir)
if not certDirValid["success"]:
return certDirValid
elif not "CN=" in self.subj:
return dict(success=False, msg="Common Name (CN) not found in subject string.")
else:
return dict(success=True)
def setup(self):
changed = False
changes = []
CURDIR = os.getcwd()
os.chdir(self.cadir)
fileOpensslCnf = "openssl.cnf"
if not os.path.exists(fileOpensslCnf):
opensslCnf = open(fileOpensslCnf, "w")
opensslCnf.write(OPENSSL_CNF)
opensslCnf.close()
changes.append("Wrote openssl.cnf file.")
changed = True
dirPrivate = "private"
if not os.path.exists(dirPrivate):
os.mkdir(dirPrivate, 0700)
changes.append("Created private directory.")
changed = True
dirCerts = "certs"
if not os.path.exists(dirCerts):
os.mkdir(dirCerts)
changes.append("Created certs directory.")
changed = True
fileSerial = "serial"
if not os.path.exists(fileSerial):
serial = open(fileSerial, "w")
serial.write("01")
serial.close()
changes.append("Created serial file.")
changed = True
fileIndexTxt = "index.txt"
if not os.path.exists(fileIndexTxt):
with file(fileIndexTxt, "a"):
os.utime(fileIndexTxt, None)
changes.append("Created index.txt file")
changed = True
fileCaCert = "cacert.pem"
if not os.path.exists(fileCaCert):
cmd = TMPL_CA_CERT.format(KEY_STRENGTH, DAYS_VALID, self.subj)
self.execute_command(cmd)
changes.append("Created CA certificate.")
changed = True
fileCaDerCert = "cacert.cer"
if not os.path.exists(fileCaDerCert):
cmd = TMPL_CONVERT
self.execute_command(cmd)
changes.append("Converted CA certificate to DER format.")
changed = True
os.chdir(CURDIR)
return dict(success=True, changed=changed, changes=changes)
def removeCA(self):
if os.path.exists(self.cadir):
shutil.rmtree(self.cadir)
return dict(success=True, changed=True, changes=["Directory '{0}' removed".format(self.cadir)])
else:
return dict(success=True, changed=False, changes=[], msg="CA directory '{0}' does not exist.".format(self.cadir))
def main():
BASE_MODULE_ARGS = dict(
certdir = dict(default="/etc/certs"),
subj = dict(default="/DC=com/DC=example/CN=CA/"),
state = dict(default="present", choices=["present", "absent"])
)
module = AnsibleModule(
argument_spec= BASE_MODULE_ARGS,
supports_check_mode=True
)
ca = CA(module.params["certdir"], module.params["subj"])
isValid = ca.validate_setup()
if isValid["success"]:
if module.params["state"] == "present":
isValid = ca.setup()
else:
isValid = ca.removeCA()
if not isValid["success"]:
module.fail_json(msg=isValid["msg"])
else:
module.exit_json(**isValid)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()