Charmed-Kubernetes/kubernetes-master/reactive/tls_client.py

209 lines
8.2 KiB
Python

import os
from pathlib import Path
from subprocess import check_call
from charms import layer
from charms.reactive import hook
from charms.reactive import set_state, remove_state
from charms.reactive import when
from charms.reactive import set_flag, clear_flag
from charms.reactive import endpoint_from_flag
from charms.reactive.helpers import data_changed
from charmhelpers.core import hookenv, unitdata
from charmhelpers.core.hookenv import log
@when('certificates.ca.available')
def store_ca(tls):
'''Read the certificate authority from the relation object and install
the ca on this system.'''
# Get the CA from the relationship object.
certificate_authority = tls.get_ca()
if certificate_authority:
layer_options = layer.options('tls-client')
ca_path = layer_options.get('ca_certificate_path')
changed = data_changed('certificate_authority', certificate_authority)
if ca_path:
if changed or not os.path.exists(ca_path):
log('Writing CA certificate to {0}'.format(ca_path))
# ensure we have a newline at the end of the certificate.
# some things will blow up without one.
# See https://bugs.launchpad.net/charm-kubernetes-master/+bug/1828034
if not certificate_authority.endswith('\n'):
certificate_authority += '\n'
_write_file(ca_path, certificate_authority)
set_state('tls_client.ca.written')
set_state('tls_client.ca.saved')
if changed:
# Update /etc/ssl/certs and generate ca-certificates.crt
install_ca(certificate_authority)
@when('certificates.server.cert.available')
def store_server(tls):
'''Read the server certificate and server key from the relation object
and save them to the certificate directory..'''
server_cert, server_key = tls.get_server_cert()
chain = tls.get_chain()
if chain:
server_cert = server_cert + '\n' + chain
if server_cert and server_key:
layer_options = layer.options('tls-client')
cert_path = layer_options.get('server_certificate_path')
key_path = layer_options.get('server_key_path')
cert_changed = data_changed('server_certificate', server_cert)
key_changed = data_changed('server_key', server_key)
if cert_path:
if cert_changed or not os.path.exists(cert_path):
log('Writing server certificate to {0}'.format(cert_path))
_write_file(cert_path, server_cert)
set_state('tls_client.server.certificate.written')
set_state('tls_client.server.certificate.saved')
if key_path:
if key_changed or not os.path.exists(key_path):
log('Writing server key to {0}'.format(key_path))
_write_file(key_path, server_key)
set_state('tls_client.server.key.saved')
@when('certificates.client.cert.available')
def store_client(tls):
'''Read the client certificate and client key from the relation object
and copy them to the certificate directory.'''
client_cert, client_key = tls.get_client_cert()
chain = tls.get_chain()
if chain:
client_cert = client_cert + '\n' + chain
if client_cert and client_key:
layer_options = layer.options('tls-client')
cert_path = layer_options.get('client_certificate_path')
key_path = layer_options.get('client_key_path')
cert_changed = data_changed('client_certificate', client_cert)
key_changed = data_changed('client_key', client_key)
if cert_path:
if cert_changed or not os.path.exists(cert_path):
log('Writing client certificate to {0}'.format(cert_path))
_write_file(cert_path, client_cert)
set_state('tls_client.client.certificate.written')
set_state('tls_client.client.certificate.saved')
if key_path:
if key_changed or not os.path.exists(key_path):
log('Writing client key to {0}'.format(key_path))
_write_file(key_path, client_key)
set_state('tls_client.client.key.saved')
@when('certificates.certs.changed')
def update_certs():
tls = endpoint_from_flag('certificates.certs.changed')
certs_paths = unitdata.kv().get('layer.tls-client.cert-paths', {})
all_ready = True
any_changed = False
maps = {
'server': tls.server_certs_map,
'client': tls.client_certs_map,
}
if maps.get('client') == {}:
log(
'No client certs found using maps. Checking for global \
client certificates.',
'WARNING'
)
# Check for global certs,
# Backwards compatibility https://bugs.launchpad.net/charm-kubernetes-master/+bug/1825819
cert_pair = tls.get_client_cert()
if cert_pair is not None:
for client_name in certs_paths.get('client', {}).keys():
maps.get('client').update({
client_name: cert_pair
})
chain = tls.get_chain()
for cert_type in ('server', 'client'):
for common_name, paths in certs_paths.get(cert_type, {}).items():
cert_pair = maps[cert_type].get(common_name)
if not cert_pair:
all_ready = False
continue
if not data_changed('layer.tls-client.'
'{}.{}'.format(cert_type, common_name), cert_pair):
continue
cert = None
key = None
if type(cert_pair) is not tuple:
if paths['crt']:
cert = cert_pair.cert
if paths['key']:
key = cert_pair.key
else:
cert, key = cert_pair
if cert:
if chain:
cert = cert + '\n' + chain
_ensure_directory(paths['crt'])
Path(paths['crt']).write_text(cert)
if key:
_ensure_directory(paths['key'])
Path(paths['key']).write_text(key)
any_changed = True
# clear flags first to ensure they are re-triggered if left set
clear_flag('tls_client.{}.certs.changed'.format(cert_type))
clear_flag('tls_client.{}.cert.{}.changed'.format(cert_type,
common_name))
set_flag('tls_client.{}.certs.changed'.format(cert_type))
set_flag('tls_client.{}.cert.{}.changed'.format(cert_type,
common_name))
if all_ready:
set_flag('tls_client.certs.saved')
if any_changed:
clear_flag('tls_client.certs.changed')
set_flag('tls_client.certs.changed')
clear_flag('certificates.certs.changed')
def install_ca(certificate_authority):
'''Install a certificiate authority on the system by calling the
update-ca-certificates command.'''
if certificate_authority:
name = hookenv.service_name()
# Create a path to install CAs on Debian systems.
ca_path = '/usr/local/share/ca-certificates/{0}.crt'.format(name)
log('Writing CA certificate to {0}'.format(ca_path))
_write_file(ca_path, certificate_authority)
# Update the trusted CAs on this system (a time expensive operation).
check_call(['update-ca-certificates'])
log('Generated ca-certificates.crt for {0}'.format(name))
set_state('tls_client.ca_installed')
@hook('upgrade-charm')
def remove_states():
remove_state('tls_client.ca.saved')
remove_state('tls_client.server.certificate.saved')
remove_state('tls_client.server.key.saved')
remove_state('tls_client.client.certificate.saved')
remove_state('tls_client.client.key.saved')
def _ensure_directory(path):
'''Ensure the parent directory exists creating directories if necessary.'''
directory = os.path.dirname(path)
if not os.path.isdir(directory):
os.makedirs(directory)
os.chmod(directory, 0o770)
def _write_file(path, content):
'''Write the path to a file.'''
_ensure_directory(path)
with open(path, 'w') as stream:
stream.write(content)
os.chmod(path, 0o440)