Charmed-Kubernetes/kubernetes-control-plane/hooks/relations/tls-certificates/provides.py

302 lines
10 KiB
Python

if not __package__:
# fix relative imports when building docs
import sys
__package__ = sys.modules[''].__name__
from charms.reactive import Endpoint
from charms.reactive import when, when_not
from charms.reactive import set_flag, clear_flag, toggle_flag
from .tls_certificates_common import (
ApplicationCertificateRequest,
CertificateRequest
)
class TlsProvides(Endpoint):
"""
The provider's side of the interface protocol.
The following flags may be set:
* `{endpoint_name}.available`
Whenever any clients are joined.
* `{endpoint_name}.certs.requested`
When there are new certificate requests of any kind to be processed.
The requests can be accessed via [new_requests][].
* `{endpoint_name}.server.certs.requested`
When there are new server certificate requests to be processed.
The requests can be accessed via [new_server_requests][].
* `{endpoint_name}.client.certs.requested`
When there are new client certificate requests to be processed.
The requests can be accessed via [new_client_requests][].
[Certificate]: common.md#tls_certificates_common.Certificate
[CertificateRequest]: common.md#tls_certificates_common.CertificateRequest
[all_requests]: provides.md#provides.TlsProvides.all_requests
[new_requests]: provides.md#provides.TlsProvides.new_requests
[new_server_requests]: provides.md#provides.TlsProvides.new_server_requests
[new_client_requests]: provides.md#provides.TlsProvides.new_client_requests
"""
@when('endpoint.{endpoint_name}.joined')
def joined(self):
set_flag(self.expand_name('{endpoint_name}.available'))
toggle_flag(self.expand_name('{endpoint_name}.certs.requested'),
self.new_requests)
toggle_flag(self.expand_name('{endpoint_name}.server.certs.requested'),
self.new_server_requests)
toggle_flag(self.expand_name('{endpoint_name}.client.certs.requested'),
self.new_client_requests)
toggle_flag(
self.expand_name('{endpoint_name}.application.certs.requested'),
self.new_application_requests)
# For backwards compatibility, set the old "cert" flags as well
toggle_flag(self.expand_name('{endpoint_name}.server.cert.requested'),
self.new_server_requests)
toggle_flag(self.expand_name('{endpoint_name}.client.cert.requested'),
self.new_client_requests)
@when_not('endpoint.{endpoint_name}.joined')
def broken(self):
clear_flag(self.expand_name('{endpoint_name}.available'))
clear_flag(self.expand_name('{endpoint_name}.certs.requested'))
clear_flag(self.expand_name('{endpoint_name}.server.certs.requested'))
clear_flag(self.expand_name('{endpoint_name}.client.certs.requested'))
clear_flag(
self.expand_name('{endpoint_name}.application.certs.requested'))
def set_ca(self, certificate_authority):
"""
Publish the CA to all related applications.
"""
for relation in self.relations:
# All the clients get the same CA, so send it to them.
relation.to_publish_raw['ca'] = certificate_authority
def set_chain(self, chain):
"""
Publish the chain of trust to all related applications.
"""
for relation in self.relations:
# All the clients get the same chain, so send it to them.
relation.to_publish_raw['chain'] = chain
def set_client_cert(self, cert, key):
"""
Deprecated. This is only for backwards compatibility.
Publish a globally shared client cert and key.
"""
for relation in self.relations:
relation.to_publish_raw.update({
'client.cert': cert,
'client.key': key,
})
def set_server_cert(self, scope, cert, key):
"""
Deprecated. Use one of the [new_requests][] collections and
`request.set_cert()` instead.
Set the server cert and key for the request identified by `scope`.
"""
request = self.get_server_requests()[scope]
request.set_cert(cert, key)
def set_server_multicerts(self, scope):
"""
Deprecated. Done automatically.
"""
pass
def add_server_cert(self, scope, cn, cert, key):
'''
Deprecated. Use `request.set_cert()` instead.
'''
self.set_server_cert(scope, cert, key)
def get_server_requests(self):
"""
Deprecated. Use the [new_requests][] or [server_requests][]
collections instead.
One provider can have many requests to generate server certificates.
Return a map of all server request objects indexed by a unique
identifier.
"""
return {req._key: req for req in self.new_server_requests}
@property
def all_requests(self):
"""
List of all requests that have been made.
Each will be an instance of [CertificateRequest][].
Example usage:
```python
@when('certs.regen',
'tls.certs.available')
def regen_all_certs():
tls = endpoint_from_flag('tls.certs.available')
for request in tls.all_requests:
cert, key = generate_cert(request.cert_type,
request.common_name,
request.sans)
request.set_cert(cert, key)
```
"""
requests = []
for unit in self.all_joined_units:
# handle older single server cert request
if unit.received_raw['common_name']:
requests.append(CertificateRequest(
unit,
'server',
unit.received_raw['certificate_name'],
unit.received_raw['common_name'],
unit.received['sans'],
))
# handle mutli server cert requests
reqs = unit.received['cert_requests'] or {}
for common_name, req in reqs.items():
requests.append(CertificateRequest(
unit,
'server',
common_name,
common_name,
req['sans'],
))
# handle client cert requests
reqs = unit.received['client_cert_requests'] or {}
for common_name, req in reqs.items():
requests.append(CertificateRequest(
unit,
'client',
common_name,
common_name,
req['sans'],
))
# handle application cert requests
reqs = unit.received['application_cert_requests'] or {}
for common_name, req in reqs.items():
requests.append(ApplicationCertificateRequest(
unit,
'application',
common_name,
common_name,
req['sans']
))
return requests
@property
def new_requests(self):
"""
Filtered view of [all_requests][] that only includes requests that
haven't been handled.
Each will be an instance of [CertificateRequest][].
This collection can also be further filtered by request type using
[new_server_requests][] or [new_client_requests][].
Example usage:
```python
@when('tls.certs.requested')
def gen_certs():
tls = endpoint_from_flag('tls.certs.requested')
for request in tls.new_requests:
cert, key = generate_cert(request.cert_type,
request.common_name,
request.sans)
request.set_cert(cert, key)
```
"""
return [req for req in self.all_requests if not req.is_handled]
@property
def new_server_requests(self):
"""
Filtered view of [new_requests][] that only includes server cert
requests.
Each will be an instance of [CertificateRequest][].
Example usage:
```python
@when('tls.server.certs.requested')
def gen_server_certs():
tls = endpoint_from_flag('tls.server.certs.requested')
for request in tls.new_server_requests:
cert, key = generate_server_cert(request.common_name,
request.sans)
request.set_cert(cert, key)
```
"""
return [req for req in self.new_requests if req.cert_type == 'server']
@property
def new_client_requests(self):
"""
Filtered view of [new_requests][] that only includes client cert
requests.
Each will be an instance of [CertificateRequest][].
Example usage:
```python
@when('tls.client.certs.requested')
def gen_client_certs():
tls = endpoint_from_flag('tls.client.certs.requested')
for request in tls.new_client_requests:
cert, key = generate_client_cert(request.common_name,
request.sans)
request.set_cert(cert, key)
```
"""
return [req for req in self.new_requests if req.cert_type == 'client']
@property
def new_application_requests(self):
"""
Filtered view of [new_requests][] that only includes application cert
requests.
Each will be an instance of [ApplicationCertificateRequest][].
Example usage:
```python
@when('tls.application.certs.requested')
def gen_application_certs():
tls = endpoint_from_flag('tls.application.certs.requested')
for request in tls.new_application_requests:
cert, key = generate_application_cert(request.common_name,
request.sans)
request.set_cert(cert, key)
```
:returns: List of certificate requests.
:rtype: [CertificateRequest, ]
"""
return [req for req in self.new_requests
if req.cert_type == 'application']
@property
def all_published_certs(self):
"""
List of all [Certificate][] instances that this provider has published
for all related applications.
"""
return [req.cert for req in self.all_requests if req.cert]