promenade/promenade/generator.py

158 lines
5.3 KiB
Python

from . import exceptions, logging, pki
import collections
import itertools
import os
import yaml
__all__ = ['Generator']
LOG = logging.getLogger(__name__)
class Generator:
def __init__(self, config, block_strings=True):
self.config = config
self.keys = pki.PKI(block_strings=block_strings)
self.outputs = collections.defaultdict(dict)
@property
def cluster_domain(self):
return self.config['KubernetesNetwork:dns.cluster_domain']
def generate(self, output_dir=None):
for catalog in self.config.iterate(kind='PKICatalog'):
for ca_name, ca_def in catalog['data'].get(
'certificate_authorities', {}).items():
ca_cert, ca_key = self.get_or_gen_ca(ca_name)
for cert_def in ca_def.get('certificates', []):
document_name = cert_def['document_name']
cert, key = self.get_or_gen_cert(
document_name,
ca_cert=ca_cert,
ca_key=ca_key,
cn=cert_def['common_name'],
hosts=_extract_hosts(cert_def),
groups=cert_def.get('groups', []))
for keypair_def in catalog['data'].get('keypairs', []):
document_name = keypair_def['name']
self.get_or_gen_keypair(document_name)
if output_dir:
self._write(output_dir)
def get_or_gen_ca(self, document_name):
kinds = [
'CertificateAuthority',
'CertificateAuthorityKey',
]
return self._get_or_gen(self.gen_ca, kinds, document_name)
def get_or_gen_cert(self, document_name, **kwargs):
kinds = [
'Certificate',
'CertificateKey',
]
return self._get_or_gen(self.gen_cert, kinds, document_name, **kwargs)
def get_or_gen_keypair(self, document_name):
kinds = [
'PublicKey',
'PrivateKey',
]
return self._get_or_gen(self.gen_keypair, kinds, document_name)
def gen_ca(self, document_name, **kwargs):
return self.keys.generate_ca(document_name, **kwargs)
def gen_cert(self, document_name, *, ca_cert, ca_key, **kwargs):
ca_cert_data = ca_cert['data']
ca_key_data = ca_key['data']
return self.keys.generate_certificate(
document_name, ca_cert=ca_cert_data, ca_key=ca_key_data, **kwargs)
def gen_keypair(self, document_name):
return self.keys.generate_keypair(document_name)
def _get_or_gen(self, generator, kinds, document_name, *args, **kwargs):
docs = self._find_docs(kinds, document_name)
if not docs:
docs = generator(document_name, *args, **kwargs)
# Adding these to output should be idempotent, so we use a dict.
for doc in docs:
self.outputs[doc['schema']][doc['metadata']['name']] = doc
return docs
def _find_docs(self, kinds, document_name):
schemas = ['deckhand/%s/v1' % k for k in kinds]
docs = self._find_in_config(schemas, document_name)
if docs:
if len(docs) == len(kinds):
LOG.debug('Found docs in input config named %s, kinds: %s',
document_name, kinds)
return docs
else:
raise exceptions.IncompletePKIPairError(
'Incomplete set %s '
'for name: %s' % (kinds, document_name))
else:
docs = self._find_in_outputs(schemas, document_name)
if docs:
LOG.debug('Found docs in current outputs named %s, kinds: %s',
document_name, kinds)
return docs
else:
LOG.debug('No docs existing docs named %s, kinds: %s',
document_name, kinds)
return []
def _find_in_config(self, schemas, document_name):
result = []
for schema in schemas:
doc = self.config.find(schema=schema, name=document_name)
if doc:
result.append(doc)
return result
def _find_in_outputs(self, schemas, document_name):
result = []
for schema in schemas:
if document_name in self.outputs.get(schema, {}):
result.append(self.outputs[schema][document_name])
return result
def _write(self, output_dir):
documents = self.get_documents()
with open(os.path.join(output_dir, 'certificates.yaml'), 'w') as f:
# Don't use safe_dump_all so we can block format certificate data.
yaml.dump_all(
documents,
stream=f,
default_flow_style=False,
explicit_start=True,
indent=2)
def get_documents(self):
return list(
itertools.chain.from_iterable(
v.values() for v in self.outputs.values()))
def get_host_list(service_names):
service_list = []
for service in service_names:
parts = service.split('.')
for i in range(len(parts)):
service_list.append('.'.join(parts[:i + 1]))
return service_list
def _extract_hosts(cert_def):
hosts = cert_def.get('hosts', [])
hosts.extend(get_host_list(cert_def.get('kubernetes_service_names', [])))
return hosts