343 lines
13 KiB
Python
343 lines
13 KiB
Python
if not __package__:
|
|
# fix relative imports when building docs
|
|
import sys
|
|
__package__ = sys.modules[''].__name__
|
|
|
|
import uuid
|
|
|
|
from charmhelpers.core import hookenv
|
|
|
|
from charms.reactive import when, when_not
|
|
from charms.reactive import set_flag, clear_flag, toggle_flag
|
|
from charms.reactive import Endpoint
|
|
from charms.reactive import data_changed
|
|
|
|
from .tls_certificates_common import Certificate
|
|
|
|
|
|
class TlsRequires(Endpoint):
|
|
"""
|
|
The client's side of the interface protocol.
|
|
|
|
The following flags may be set:
|
|
|
|
* `{endpoint_name}.available`
|
|
Whenever the relation is joined.
|
|
|
|
* `{endpoint_name}.ca.available`
|
|
When the root CA information is available via the [root_ca_cert][] and
|
|
[root_ca_chain][] properties.
|
|
|
|
* `{endpoint_name}.ca.changed`
|
|
When the root CA information has changed, whether because
|
|
they have just become available or if they were regenerated by the CA.
|
|
Once processed this flag should be removed by the charm.
|
|
|
|
* `{endpoint_name}.certs.available`
|
|
When the requested server or client certs are available.
|
|
|
|
* `{endpoint_name}.certs.changed`
|
|
When the requested server or client certs have changed, whether because
|
|
they have just become available or if they were regenerated by the CA.
|
|
Once processed this flag should be removed by the charm.
|
|
|
|
* `{endpoint_name}.server.certs.available`
|
|
When the server certificates requested by [request_server_cert][] are
|
|
available via the [server_certs][] collection.
|
|
|
|
* `{endpoint_name}.server.certs.changed`
|
|
When the requested server certificates have changed, whether because
|
|
they have just become available or if they were regenerated by the CA.
|
|
Once processed this flag should be removed by the charm.
|
|
|
|
* `{endpoint_name}.client.certs.available`
|
|
When the client certificates requested by [request_client_cert][] are
|
|
available via the [client_certs][] collection.
|
|
|
|
* `{endpoint_name}.client.certs.changed`
|
|
When the requested client certificates have changed, whether because
|
|
they have just become available or if they were regenerated by the CA.
|
|
Once processed this flag should be removed by the charm.
|
|
|
|
The following flags have been deprecated:
|
|
|
|
* `{endpoint_name}.server.cert.available`
|
|
* `{endpoint_name}.client.cert.available`
|
|
* `{endpoint_name}.batch.cert.available`
|
|
|
|
[Certificate]: common.md#tls_certificates_common.Certificate
|
|
[CertificateRequest]: common.md#tls_certificates_common.CertificateRequest
|
|
[root_ca_cert]: requires.md#requires.TlsRequires.root_ca_cert
|
|
[root_ca_chain]: requires.md#requires.TlsRequires.root_ca_chain
|
|
[request_server_cert]: requires.md#requires.TlsRequires.request_server_cert
|
|
[request_client_cert]: requires.md#requires.TlsRequires.request_client_cert
|
|
[server_certs]: requires.md#requires.TlsRequires.server_certs
|
|
[server_certs_map]: requires.md#requires.TlsRequires.server_certs_map
|
|
[client_certs]: requires.md#requires.TlsRequires.server_certs
|
|
"""
|
|
|
|
@when('endpoint.{endpoint_name}.joined')
|
|
def joined(self):
|
|
self.relations[0].to_publish_raw['unit_name'] = self._unit_name
|
|
prefix = self.expand_name('{endpoint_name}.')
|
|
ca_available = self.root_ca_cert
|
|
ca_changed = ca_available and data_changed(prefix + 'ca',
|
|
self.root_ca_cert)
|
|
server_available = self.server_certs
|
|
server_changed = server_available and data_changed(prefix + 'servers',
|
|
self.server_certs)
|
|
client_available = self.client_certs
|
|
client_changed = client_available and data_changed(prefix + 'clients',
|
|
self.client_certs)
|
|
certs_available = server_available or client_available
|
|
certs_changed = server_changed or client_changed
|
|
|
|
set_flag(prefix + 'available')
|
|
toggle_flag(prefix + 'ca.available', ca_available)
|
|
toggle_flag(prefix + 'ca.changed', ca_changed)
|
|
toggle_flag(prefix + 'server.certs.available', server_available)
|
|
toggle_flag(prefix + 'server.certs.changed', server_changed)
|
|
toggle_flag(prefix + 'client.certs.available', client_available)
|
|
toggle_flag(prefix + 'client.certs.changed', client_changed)
|
|
toggle_flag(prefix + 'certs.available', certs_available)
|
|
toggle_flag(prefix + 'certs.changed', certs_changed)
|
|
# deprecated
|
|
toggle_flag(prefix + 'server.cert.available', self.server_certs)
|
|
toggle_flag(prefix + 'client.cert.available', self.get_client_cert())
|
|
toggle_flag(prefix + 'batch.cert.available', self.server_certs)
|
|
|
|
@when_not('endpoint.{endpoint_name}.joined')
|
|
def broken(self):
|
|
prefix = self.expand_name('{endpoint_name}.')
|
|
clear_flag(prefix + 'available')
|
|
clear_flag(prefix + 'ca.available')
|
|
clear_flag(prefix + 'ca.changed')
|
|
clear_flag(prefix + 'server.certs.available')
|
|
clear_flag(prefix + 'server.certs.changed')
|
|
clear_flag(prefix + 'client.certs.available')
|
|
clear_flag(prefix + 'client.certs.changed')
|
|
clear_flag(prefix + 'certs.available')
|
|
clear_flag(prefix + 'certs.changed')
|
|
# deprecated
|
|
clear_flag(prefix + 'server.cert.available')
|
|
clear_flag(prefix + 'client.cert.available')
|
|
clear_flag(prefix + 'batch.cert.available')
|
|
|
|
@property
|
|
def _unit_name(self):
|
|
return hookenv.local_unit().replace('/', '_')
|
|
|
|
@property
|
|
def root_ca_cert(self):
|
|
"""
|
|
Root CA certificate.
|
|
"""
|
|
# only the leader of the provider should set the CA, or all units
|
|
# had better agree
|
|
return self.all_joined_units.received_raw['ca']
|
|
|
|
def get_ca(self):
|
|
"""
|
|
Return the root CA certificate.
|
|
|
|
Same as [root_ca_cert][].
|
|
"""
|
|
return self.root_ca_cert
|
|
|
|
@property
|
|
def root_ca_chain(self):
|
|
"""
|
|
The chain of trust for the root CA.
|
|
"""
|
|
# only the leader of the provider should set the CA, or all units
|
|
# had better agree
|
|
return self.all_joined_units.received_raw['chain']
|
|
|
|
def get_chain(self):
|
|
"""
|
|
Return the chain of trust for the root CA.
|
|
|
|
Same as [root_ca_chain][].
|
|
"""
|
|
return self.root_ca_chain
|
|
|
|
def get_client_cert(self):
|
|
"""
|
|
Deprecated. Use [request_client_cert][] and the [client_certs][]
|
|
collection instead.
|
|
|
|
Return a globally shared client certificate and key.
|
|
"""
|
|
data = self.all_joined_units.received_raw
|
|
return (data['client.cert'], data['client.key'])
|
|
|
|
def get_server_cert(self):
|
|
"""
|
|
Deprecated. Use the [server_certs][] collection instead.
|
|
|
|
Return the cert and key of the first server certificate requested.
|
|
"""
|
|
if not self.server_certs:
|
|
return (None, None)
|
|
cert = self.server_certs[0]
|
|
return (cert.cert, cert.key)
|
|
|
|
@property
|
|
def server_certs(self):
|
|
"""
|
|
List of [Certificate][] instances for all available server certs.
|
|
"""
|
|
certs = []
|
|
raw_data = self.all_joined_units.received_raw
|
|
json_data = self.all_joined_units.received
|
|
|
|
# for backwards compatibility, the first cert goes in its own fields
|
|
if self.relations:
|
|
common_name = self.relations[0].to_publish_raw['common_name']
|
|
cert = raw_data['{}.server.cert'.format(self._unit_name)]
|
|
key = raw_data['{}.server.key'.format(self._unit_name)]
|
|
if cert and key:
|
|
certs.append(Certificate('server',
|
|
common_name,
|
|
cert,
|
|
key))
|
|
|
|
# subsequent requests go in the collection
|
|
field = '{}.processed_requests'.format(self._unit_name)
|
|
certs_data = json_data[field] or {}
|
|
certs.extend(Certificate('server',
|
|
common_name,
|
|
cert['cert'],
|
|
cert['key'])
|
|
for common_name, cert in certs_data.items())
|
|
return certs
|
|
|
|
@property
|
|
def application_certs(self):
|
|
"""
|
|
List containg the application Certificate cert.
|
|
|
|
:returns: A list containing one certificate
|
|
:rtype: [Certificate()]
|
|
"""
|
|
certs = []
|
|
json_data = self.all_joined_units.received
|
|
field = '{}.processed_application_requests'.format(self._unit_name)
|
|
certs_data = json_data[field] or {}
|
|
app_cert_data = certs_data.get('app_data')
|
|
if app_cert_data:
|
|
certs = [Certificate(
|
|
'server',
|
|
'app_data',
|
|
app_cert_data['cert'],
|
|
app_cert_data['key'])]
|
|
return certs
|
|
|
|
@property
|
|
def server_certs_map(self):
|
|
"""
|
|
Mapping of server [Certificate][] instances by their `common_name`.
|
|
"""
|
|
return {cert.common_name: cert for cert in self.server_certs}
|
|
|
|
def get_batch_requests(self):
|
|
"""
|
|
Deprecated. Use [server_certs_map][] instead.
|
|
|
|
Mapping of server [Certificate][] instances by their `common_name`.
|
|
"""
|
|
return self.server_certs_map
|
|
|
|
@property
|
|
def client_certs(self):
|
|
"""
|
|
List of [Certificate][] instances for all available client certs.
|
|
"""
|
|
field = '{}.processed_client_requests'.format(self._unit_name)
|
|
certs_data = self.all_joined_units.received[field] or {}
|
|
return [Certificate('client',
|
|
common_name,
|
|
cert['cert'],
|
|
cert['key'])
|
|
for common_name, cert in certs_data.items()]
|
|
|
|
@property
|
|
def client_certs_map(self):
|
|
"""
|
|
Mapping of client [Certificate][] instances by their `common_name`.
|
|
"""
|
|
return {cert.common_name: cert for cert in self.client_certs}
|
|
|
|
def request_server_cert(self, cn, sans=None, cert_name=None):
|
|
"""
|
|
Request a server certificate and key be generated for the given
|
|
common name (`cn`) and optional list of alternative names (`sans`).
|
|
|
|
The `cert_name` is deprecated and not needed.
|
|
|
|
This can be called multiple times to request more than one server
|
|
certificate, although the common names must be unique. If called
|
|
again with the same common name, it will be ignored.
|
|
"""
|
|
if not self.relations:
|
|
return
|
|
# assume we'll only be connected to one provider
|
|
to_publish_json = self.relations[0].to_publish
|
|
to_publish_raw = self.relations[0].to_publish_raw
|
|
if to_publish_raw['common_name'] in (None, '', cn):
|
|
# for backwards compatibility, first request goes in its own fields
|
|
to_publish_raw['common_name'] = cn
|
|
to_publish_json['sans'] = sans or []
|
|
cert_name = to_publish_raw.get('certificate_name') or cert_name
|
|
if cert_name is None:
|
|
cert_name = str(uuid.uuid4())
|
|
to_publish_raw['certificate_name'] = cert_name
|
|
else:
|
|
# subsequent requests go in the collection
|
|
requests = to_publish_json.get('cert_requests', {})
|
|
requests[cn] = {'sans': sans or []}
|
|
to_publish_json['cert_requests'] = requests
|
|
|
|
def add_request_server_cert(self, cn, sans):
|
|
"""
|
|
Deprecated. Use [request_server_cert][] instead.
|
|
"""
|
|
self.request_server_cert(cn, sans)
|
|
|
|
def request_server_certs(self):
|
|
"""
|
|
Deprecated. Just use [request_server_cert][]; this does nothing.
|
|
"""
|
|
pass
|
|
|
|
def request_client_cert(self, cn, sans):
|
|
"""
|
|
Request a client certificate and key be generated for the given
|
|
common name (`cn`) and list of alternative names (`sans`).
|
|
|
|
This can be called multiple times to request more than one client
|
|
certificate, although the common names must be unique. If called
|
|
again with the same common name, it will be ignored.
|
|
"""
|
|
if not self.relations:
|
|
return
|
|
# assume we'll only be connected to one provider
|
|
to_publish_json = self.relations[0].to_publish
|
|
requests = to_publish_json.get('client_cert_requests', {})
|
|
requests[cn] = {'sans': sans}
|
|
to_publish_json['client_cert_requests'] = requests
|
|
|
|
def request_application_cert(self, cn, sans):
|
|
"""
|
|
Request an application certificate and key be generated for the given
|
|
common name (`cn`) and list of alternative names (`sans` ) of this
|
|
unit and all peer units. All units will share a single certificates.
|
|
"""
|
|
if not self.relations:
|
|
return
|
|
# assume we'll only be connected to one provider
|
|
to_publish_json = self.relations[0].to_publish
|
|
requests = to_publish_json.get('application_cert_requests', {})
|
|
requests[cn] = {'sans': sans}
|
|
to_publish_json['application_cert_requests'] = requests
|