Make keystone as certificate transfer provider
Add functionality to keystone to act as a certificate transfer provider. Add actions to add, remove, list CA certs to keystone. Add Certificate Transfer requires handler in ops_sunbeam. Update keystone_auth section cafile option if certificate is available in receive-ca-cert relation. Update metadata.yaml for keystone and rest of k8s charms. Change-Id: I9c800e8f8a0c9197b195331be7b445bafe794780
This commit is contained in:
parent
dd6000bb51
commit
bd057784d5
@ -72,6 +72,9 @@ requires:
|
||||
limit: 1
|
||||
amqp:
|
||||
interface: rabbitmq
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
aodh:
|
||||
|
@ -93,7 +93,13 @@ class AODHEvaluatorPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -131,7 +137,13 @@ class AODHNotifierPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -169,7 +181,13 @@ class AODHListenerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -209,7 +227,13 @@ class AODHExpirerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"aodh",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -42,6 +42,9 @@ requires:
|
||||
vault-kv:
|
||||
interface: vault-kv
|
||||
limit: 1
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -222,7 +222,13 @@ class BarbicanWorkerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
return [
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/barbican/barbican.conf", "barbican", "barbican"
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"barbican",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
@ -464,10 +470,18 @@ class BarbicanVaultOperatorCharm(BarbicanOperatorCharm):
|
||||
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
|
||||
"""Container configuration files for the service."""
|
||||
_cconfigs = super().container_configs
|
||||
_cconfigs.append(
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
self.ca_crt_file, "barbican", "barbican"
|
||||
)
|
||||
_cconfigs.extend(
|
||||
[
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
self.ca_crt_file, "barbican", "barbican"
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"barbican",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
)
|
||||
return _cconfigs
|
||||
|
||||
|
@ -49,6 +49,9 @@ requires:
|
||||
limit: 1
|
||||
gnocchi-db:
|
||||
interface: gnocchi
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -305,6 +305,12 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
return _cconfigs
|
||||
|
||||
|
@ -57,6 +57,9 @@ requires:
|
||||
image-service:
|
||||
interface: glance
|
||||
optional: true
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -92,6 +92,12 @@ class CinderWSGIPebbleHandler(sunbeam_chandlers.WSGIPebbleHandler):
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/cinder/cinder.conf", "root", "cinder", 0o640
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"cinder",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -143,7 +149,13 @@ class CinderSchedulerPebbleHandler(sunbeam_chandlers.PebbleHandler):
|
||||
return [
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/cinder/cinder.conf", "root", "cinder", 0o640
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"cinder",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -45,6 +45,9 @@ requires:
|
||||
dns-backend:
|
||||
interface: bind-rndc
|
||||
limit: 1
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -144,6 +144,12 @@ class DesignatePebbleHandler(sunbeam_chandlers.WSGIPebbleHandler):
|
||||
"designate",
|
||||
"designate",
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"designate",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
)
|
||||
return _cconfig
|
||||
|
@ -67,6 +67,9 @@ requires:
|
||||
ceph:
|
||||
interface: ceph-client
|
||||
optional: true
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
image-service:
|
||||
|
@ -251,6 +251,12 @@ class GlanceOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
if self.has_ceph_relation():
|
||||
_cconfigs.extend(
|
||||
|
@ -51,6 +51,9 @@ requires:
|
||||
limit: 1
|
||||
ceph:
|
||||
interface: ceph-client
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
gnocchi-service:
|
||||
|
@ -280,6 +280,12 @@ class GnocchiOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
def configure_app_leader(self, event: EventBase):
|
||||
|
@ -54,6 +54,9 @@ requires:
|
||||
interface: rabbitmq
|
||||
identity-ops:
|
||||
interface: keystone-resources
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -426,6 +426,7 @@ class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
"rule": f"PathPrefix(`/{model}-{app}`)",
|
||||
"service": f"juju-{model}-{app}-service",
|
||||
"entryPoints": ["websecure"],
|
||||
"tls": {},
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -677,6 +678,12 @@ class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
def heat_api_cfn_container_configs(self):
|
||||
@ -694,6 +701,12 @@ class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
def _get_create_role_ops(self) -> list:
|
||||
|
@ -43,10 +43,13 @@ requires:
|
||||
identity-credentials:
|
||||
interface: keystone-credentials
|
||||
limit: 1
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
horizon:
|
||||
interface: horizon
|
||||
interface: horizon
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -169,6 +169,22 @@ class HorizonOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
}
|
||||
]
|
||||
|
||||
@property
|
||||
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
|
||||
"""Container configuration files for the service."""
|
||||
_cconfigs = super().container_configs
|
||||
_cconfigs.extend(
|
||||
[
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
)
|
||||
return _cconfigs
|
||||
|
||||
def get_pebble_handlers(self) -> List[sunbeam_chandlers.PebbleHandler]:
|
||||
"""Pebble handlers for the service."""
|
||||
return [
|
||||
|
@ -198,6 +198,9 @@ OPENSTACK_KEYSTONE_URL = "{{ identity_credentials.internal_protocol }}://%s:{{ i
|
||||
OPENSTACK_API_VERSIONS = { "identity": 3, }
|
||||
OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True
|
||||
OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "{{ options.default_domain or identity_credentials.project_domain_id }}"
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
OPENSTACK_SSL_CACERT = "/usr/local/share/ca-certificates/ca-bundle.pem"
|
||||
{% endif -%}
|
||||
|
||||
# Enables keystone web single-sign-on if set to True.
|
||||
#WEBSSO_ENABLED = False
|
||||
|
@ -27,3 +27,34 @@ regenerate-password:
|
||||
required:
|
||||
- username
|
||||
additionalProperties: False
|
||||
|
||||
add-ca-certs:
|
||||
description: |
|
||||
Add CA certs for transfer
|
||||
params:
|
||||
name:
|
||||
type: string
|
||||
description: Name of CA certs bundle
|
||||
ca:
|
||||
type: string
|
||||
description: Base64 encoded CA certificate
|
||||
chain:
|
||||
type: string
|
||||
description: Base64 encoded CA Chain
|
||||
required:
|
||||
- name
|
||||
- ca
|
||||
additionalProperties: False
|
||||
remove-ca-certs:
|
||||
description: |
|
||||
Remove CA certs
|
||||
params:
|
||||
name:
|
||||
type: string
|
||||
description: Name of CA certs bundle
|
||||
required:
|
||||
- name
|
||||
additionalProperties: False
|
||||
list-ca-certs:
|
||||
description: |
|
||||
List CA certs uploaded for transfer
|
||||
|
@ -29,6 +29,8 @@ provides:
|
||||
interface: keystone-credentials
|
||||
identity-ops:
|
||||
interface: keystone-resources
|
||||
send-ca-cert:
|
||||
interface: certificate_transfer
|
||||
|
||||
requires:
|
||||
database:
|
||||
|
@ -25,6 +25,8 @@ develop a new k8s charm using the Operator Framework:
|
||||
https://discourse.charmhub.io/t/4208
|
||||
"""
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import logging
|
||||
from collections import (
|
||||
@ -54,6 +56,9 @@ import ops_sunbeam.guard as sunbeam_guard
|
||||
import ops_sunbeam.job_ctrl as sunbeam_job_ctrl
|
||||
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
|
||||
import pwgen
|
||||
from charms.certificate_transfer_interface.v0.certificate_transfer import (
|
||||
CertificateTransferProvides,
|
||||
)
|
||||
from ops.charm import (
|
||||
ActionEvent,
|
||||
RelationChangedEvent,
|
||||
@ -68,10 +73,12 @@ from ops.model import (
|
||||
ActiveStatus,
|
||||
MaintenanceStatus,
|
||||
ModelError,
|
||||
Relation,
|
||||
SecretNotFoundError,
|
||||
SecretRotate,
|
||||
)
|
||||
from utils import (
|
||||
certs,
|
||||
manager,
|
||||
)
|
||||
|
||||
@ -81,8 +88,7 @@ KEYSTONE_CONTAINER = "keystone"
|
||||
FERNET_KEYS_PREFIX = "fernet-"
|
||||
CREDENTIALS_SECRET_PREFIX = "credentials_"
|
||||
SECRET_PREFIX = "secret://"
|
||||
|
||||
|
||||
CERTIFICATE_TRANSFER_LABEL = "certs_to_transfer"
|
||||
KEYSTONE_CONF = "/etc/keystone/keystone.conf"
|
||||
LOGGING_CONF = "/etc/keystone/logging.conf"
|
||||
|
||||
@ -333,6 +339,7 @@ class KeystoneOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
IDSVC_RELATION_NAME = "identity-service"
|
||||
IDCREDS_RELATION_NAME = "identity-credentials"
|
||||
IDOPS_RELATION_NAME = "identity-ops"
|
||||
SEND_CA_CERT_RELATION_NAME = "send-ca-cert"
|
||||
|
||||
def __init__(self, framework):
|
||||
super().__init__(framework)
|
||||
@ -344,9 +351,17 @@ class KeystoneOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self._state.set_default(default_domain_id=None)
|
||||
self._state.set_default(service_project_id=None)
|
||||
|
||||
self.certificate_transfer = CertificateTransferProvides(
|
||||
self, self.SEND_CA_CERT_RELATION_NAME
|
||||
)
|
||||
|
||||
self.framework.observe(
|
||||
self.on.peers_relation_changed, self._on_peer_data_changed
|
||||
)
|
||||
self.framework.observe(
|
||||
self.on.send_ca_cert_relation_joined,
|
||||
self._handle_certificate_transfer_on_event,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.on.get_admin_password_action, self._get_admin_password_action
|
||||
)
|
||||
@ -361,6 +376,18 @@ class KeystoneOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self.on.regenerate_password_action,
|
||||
self._regenerate_password_action,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.on.add_ca_certs_action,
|
||||
self._add_ca_certs_action,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.on.remove_ca_certs_action,
|
||||
self._remove_ca_certs_action,
|
||||
)
|
||||
self.framework.observe(
|
||||
self.on.list_ca_certs_action,
|
||||
self._list_ca_certs_action,
|
||||
)
|
||||
if self.bootstrapped():
|
||||
self.bootstrap_status.set(ActiveStatus())
|
||||
|
||||
@ -515,6 +542,108 @@ export OS_AUTH_VERSION=3
|
||||
except Exception as e:
|
||||
event.fail(f"Regeneration of password failed: {e}")
|
||||
|
||||
def _create_certificate_transfer_secret(
|
||||
self, name: str, ca_cert: str, chain_certs: str
|
||||
) -> bool:
|
||||
certs_secret_id = self.peers.get_app_data(CERTIFICATE_TRANSFER_LABEL)
|
||||
if certs_secret_id:
|
||||
certs_secret = self.model.get_secret(id=certs_secret_id)
|
||||
certificates = certs_secret.get_content()
|
||||
certificates = json.loads(certificates.get("certs"))
|
||||
if name in certificates:
|
||||
return False
|
||||
|
||||
certificates[name] = {"ca": ca_cert, "chain": chain_certs}
|
||||
certs_secret.set_content({"certs": json.dumps(certificates)})
|
||||
else:
|
||||
certificates = {}
|
||||
certificates[name] = {"ca": ca_cert, "chain": chain_certs}
|
||||
certificates = {"certs": json.dumps(certificates)}
|
||||
certs_secret = self.model.app.add_secret(
|
||||
certificates, label=CERTIFICATE_TRANSFER_LABEL
|
||||
)
|
||||
self.peers.set_app_data(
|
||||
{CERTIFICATE_TRANSFER_LABEL: certs_secret.id}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _add_ca_certs_action(self, event: ActionEvent):
|
||||
"""Distribute CA certs."""
|
||||
if not self.unit.is_leader():
|
||||
event.fail("Please run action on lead unit.")
|
||||
return
|
||||
|
||||
name = event.params.get("name")
|
||||
ca = event.params.get("ca")
|
||||
chain = event.params.get("chain")
|
||||
ca_cert = None
|
||||
chain_certs = None
|
||||
|
||||
try:
|
||||
ca_bytes = base64.b64decode(ca)
|
||||
ca_cert = ca_bytes.decode()
|
||||
if not certs.certificate_is_valid(ca_bytes):
|
||||
event.fail("Invalid CA certificate")
|
||||
return
|
||||
|
||||
if chain:
|
||||
chain_bytes = base64.b64decode(chain)
|
||||
chain_certs = chain_bytes.decode()
|
||||
ca_chain_list = certs.parse_ca_chain(chain_bytes)
|
||||
for _ca in ca_chain_list:
|
||||
if not certs.certificate_is_valid(_ca):
|
||||
event.fail("Invalid certificate in CA Chain")
|
||||
return
|
||||
|
||||
if not certs.ca_chain_is_valid(ca_chain_list):
|
||||
event.fail("Invalid CA Chain")
|
||||
except (binascii.Error, TypeError, ValueError) as e:
|
||||
event.fail(str(e))
|
||||
return
|
||||
|
||||
if not self._create_certificate_transfer_secret(
|
||||
name, ca_cert, chain_certs
|
||||
):
|
||||
event.fail("Certificate bundle already transferred")
|
||||
|
||||
self._handle_certificate_transfers()
|
||||
|
||||
def _remove_ca_certs_action(self, event: ActionEvent):
|
||||
"""Remove CA certs."""
|
||||
if not self.unit.is_leader():
|
||||
event.fail("Please run action on lead unit.")
|
||||
return
|
||||
|
||||
certs_secret_id = self.peers.get_app_data(CERTIFICATE_TRANSFER_LABEL)
|
||||
if certs_secret_id:
|
||||
certs_secret = self.model.get_secret(id=certs_secret_id)
|
||||
certificates = certs_secret.get_content()
|
||||
certificates = json.loads(certificates.get("certs"))
|
||||
name = event.params.get("name")
|
||||
if name not in certificates:
|
||||
event.fail("Certificate bundle does not exist")
|
||||
return
|
||||
|
||||
certificates.pop(name)
|
||||
certs_secret.set_content({"certs": json.dumps(certificates)})
|
||||
self._handle_certificate_transfers()
|
||||
|
||||
def _list_ca_certs_action(self, event: ActionEvent):
|
||||
"""List CA certs."""
|
||||
if not self.unit.is_leader():
|
||||
event.fail("Please run action on lead unit.")
|
||||
return
|
||||
|
||||
certs_secret_id = self.peers.get_app_data(CERTIFICATE_TRANSFER_LABEL)
|
||||
if certs_secret_id:
|
||||
certs_secret = self.model.get_secret(id=certs_secret_id)
|
||||
certificates = certs_secret.get_content()
|
||||
certificates = json.loads(certificates.get("certs"))
|
||||
event.set_results(certificates)
|
||||
else:
|
||||
event.set_results({})
|
||||
|
||||
def _on_peer_data_changed(self, event: RelationChangedEvent):
|
||||
"""Process fernet updates if possible."""
|
||||
if self._state.unit_bootstrapped and self.is_leader_ready():
|
||||
@ -1585,6 +1714,84 @@ export OS_AUTH_VERSION=3
|
||||
relation_id, relation_name, ops_response=response
|
||||
)
|
||||
|
||||
def _get_combined_ca_and_chain(self, certs_secret=None) -> (str, list):
|
||||
"""Combine all certs for CA and chain.
|
||||
|
||||
Action add-ca-certs allows to add multiple CA cert and chain certs.
|
||||
Combine all CA certs in the secret and chains in the secret.
|
||||
"""
|
||||
if not certs_secret:
|
||||
certs_secret_id = self.peers.get_app_data(
|
||||
CERTIFICATE_TRANSFER_LABEL
|
||||
)
|
||||
if not certs_secret_id:
|
||||
logger.debug("No certificates to transfer")
|
||||
return "", []
|
||||
|
||||
certs_secret = self.model.get_secret(id=certs_secret_id)
|
||||
certificates = certs_secret.get_content()
|
||||
certificates = json.loads(certificates.get("certs"))
|
||||
|
||||
if not certificates:
|
||||
logger.debug("No certificates to transfer")
|
||||
return "", []
|
||||
|
||||
ca_list = []
|
||||
chain_list = []
|
||||
for name, bundle in certificates.items():
|
||||
_ca = bundle.get("ca")
|
||||
_chain = bundle.get("chain")
|
||||
if _ca:
|
||||
ca_list.append(_ca)
|
||||
if _chain:
|
||||
chain_list.append(_chain)
|
||||
|
||||
ca = "\n".join(ca_list)
|
||||
# chain sent as list of single string containing complete chain
|
||||
chain = []
|
||||
if chain:
|
||||
chain = ["\n".join(chain_list)]
|
||||
|
||||
return ca, chain
|
||||
|
||||
def _handle_certificate_transfers(
|
||||
self, relations: List[Relation] | None = None
|
||||
):
|
||||
"""Transfer certs on given relations.
|
||||
|
||||
If relation is not specified, send on all the send-ca-cert
|
||||
relations.
|
||||
"""
|
||||
if not relations:
|
||||
relations = [
|
||||
relation
|
||||
for relation in self.framework.model.relations[
|
||||
self.SEND_CA_CERT_RELATION_NAME
|
||||
]
|
||||
]
|
||||
|
||||
ca, chain = self._get_combined_ca_and_chain()
|
||||
|
||||
for relation in relations:
|
||||
logger.debug(
|
||||
"Transferring certificates for relation "
|
||||
f"{relation.app.name} {relation.name}/{relation.id}"
|
||||
)
|
||||
self.certificate_transfer.set_certificate(
|
||||
certificate="",
|
||||
ca=ca,
|
||||
chain=chain,
|
||||
relation_id=relation.id,
|
||||
)
|
||||
|
||||
def _handle_certificate_transfer_on_event(self, event):
|
||||
if not self.unit.is_leader():
|
||||
logger.debug("Skipping send ca cert as unit is not leader.")
|
||||
return
|
||||
|
||||
logger.debug(f"Handling send ca cert event: {event}")
|
||||
self._handle_certificate_transfers([event.relation])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(KeystoneOperatorCharm)
|
||||
|
105
charms/keystone-k8s/src/utils/certs.py
Normal file
105
charms/keystone-k8s/src/utils/certs.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright 2024 Canonical Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Helper functions to verify certificates."""
|
||||
|
||||
# Helper functions are picked from
|
||||
# https://github.com/canonical/manual-tls-certificates-operator/blob/main/src/helpers.py
|
||||
|
||||
import logging
|
||||
import re
|
||||
from typing import (
|
||||
List,
|
||||
)
|
||||
|
||||
from cryptography import (
|
||||
x509,
|
||||
)
|
||||
from cryptography.exceptions import (
|
||||
InvalidSignature,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def certificate_is_valid(certificate: bytes) -> bool:
|
||||
"""Returns whether a certificate is valid.
|
||||
|
||||
Args:
|
||||
certificate: Certificate in bytes
|
||||
|
||||
Returns:
|
||||
bool: True/False
|
||||
"""
|
||||
try:
|
||||
x509.load_pem_x509_certificate(certificate)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def parse_ca_chain(ca_chain_pem: str) -> List[str]:
|
||||
"""Returns list of certificates based on a PEM CA Chain file.
|
||||
|
||||
Args:
|
||||
ca_chain_pem (str): String containing list of certificates. This string should look like:
|
||||
-----BEGIN CERTIFICATE-----
|
||||
<cert 1>
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
<cert 2>
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
Returns:
|
||||
list: List of certificates
|
||||
"""
|
||||
chain_list = re.findall(
|
||||
pattern="(?=-----BEGIN CERTIFICATE-----)(.*?)(?<=-----END CERTIFICATE-----)",
|
||||
string=ca_chain_pem,
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
if not chain_list:
|
||||
raise ValueError("No certificate found in chain file")
|
||||
return chain_list
|
||||
|
||||
|
||||
def ca_chain_is_valid(ca_chain: List[str]) -> bool:
|
||||
"""Returns whether a ca chain is valid.
|
||||
|
||||
It uses the x509 certificate method verify_directly_issued_by, which checks
|
||||
the certificate issuer name matches the issuer subject name and that
|
||||
the certificate is signed by the issuer's private key.
|
||||
|
||||
Args:
|
||||
ca_chain: composed by a list of certificates.
|
||||
|
||||
Returns:
|
||||
whether the ca chain is valid.
|
||||
"""
|
||||
if len(ca_chain) < 2:
|
||||
logger.warning(
|
||||
"Invalid CA chain: It must contain at least 2 certificates."
|
||||
)
|
||||
return False
|
||||
for ca_cert, cert in zip(ca_chain, ca_chain[1:]):
|
||||
try:
|
||||
ca_cert_object = x509.load_pem_x509_certificate(
|
||||
ca_cert.encode("utf-8")
|
||||
)
|
||||
cert_object = x509.load_pem_x509_certificate(cert.encode("utf-8"))
|
||||
cert_object.verify_directly_issued_by(ca_cert_object)
|
||||
except (ValueError, TypeError, InvalidSignature) as e:
|
||||
logger.warning("Invalid CA chain: %s", e)
|
||||
return False
|
||||
return True
|
@ -51,6 +51,9 @@ requires:
|
||||
limit: 1
|
||||
amqp:
|
||||
interface: rabbitmq
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -115,6 +115,12 @@ class MagnumConductorPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"magnum",
|
||||
"magnum",
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"magnum",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
@ -232,6 +238,12 @@ class MagnumOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
"magnum",
|
||||
"magnum",
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"magnum",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
)
|
||||
return _cconfigs
|
||||
|
@ -13,7 +13,7 @@ db_auto_create = false
|
||||
{% include "parts/section-identity" %}
|
||||
|
||||
[keystone_auth]
|
||||
{% include "parts/identity-data" %}
|
||||
auth_section = keystone_authtoken
|
||||
|
||||
{% include "parts/section-service-user" %}
|
||||
|
||||
@ -32,3 +32,28 @@ region_name = RegionOne
|
||||
api_paste_config=/etc/magnum/api-paste.ini
|
||||
|
||||
{% include "parts/section-oslo-messaging-rabbit" %}
|
||||
|
||||
[glance_client]
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
ca_file = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
|
||||
[heat_client]
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
ca_file = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
|
||||
[neutron_client]
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
ca_file = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
|
||||
[nova_client]
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
ca_file = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
|
||||
[octavia_client]
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
ca_file = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
|
@ -57,6 +57,9 @@ requires:
|
||||
certificates:
|
||||
interface: tls-certificates
|
||||
optional: true
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -91,6 +91,12 @@ class NeutronServerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/neutron/api-paste.ini", "neutron", "neutron"
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"neutron",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -235,6 +241,12 @@ class NeutronServerOVNPebbleHandler(NeutronServerPebbleHandler):
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/neutron/api-paste.ini", "root", "neutron", 0o640
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"neutron",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
@ -80,6 +80,9 @@ requires:
|
||||
interface: neutron-api
|
||||
placement:
|
||||
interface: placement
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
cloud-controller:
|
||||
|
@ -101,7 +101,13 @@ class NovaSchedulerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"root",
|
||||
"nova",
|
||||
0o640,
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"nova",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
@ -148,7 +154,13 @@ class NovaConductorPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"root",
|
||||
"nova",
|
||||
0o640,
|
||||
)
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"nova",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@ -375,6 +387,12 @@ class NovaOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
"nova",
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
"nova",
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/root/cell_create_wrapper.sh", "root", "root", 0o755
|
||||
),
|
||||
|
@ -76,6 +76,9 @@ requires:
|
||||
identity-ops:
|
||||
interface: keystone-resources
|
||||
optional: true
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
peers:
|
||||
peers:
|
||||
|
@ -221,6 +221,12 @@ class OctaviaOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
def handle_keystone_ops(self, event: ops.EventBase) -> None:
|
||||
|
@ -28,6 +28,9 @@ resources:
|
||||
requires:
|
||||
identity-ops:
|
||||
interface: keystone-resources
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
metrics-endpoint:
|
||||
|
@ -161,6 +161,12 @@ class OSExporterOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
|
||||
"_daemon_",
|
||||
"_daemon_",
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"_daemon_",
|
||||
"_daemon_",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
|
@ -10,5 +10,8 @@ clouds:
|
||||
project_domain_name: {{ os_exporter.domain_name }}
|
||||
user_domain_name: {{ os_exporter.domain_name }}
|
||||
auth_url: {{ os_exporter.auth_url }}
|
||||
# cacert: /etc/ssl/ca.pem
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
cacert: {{ receive_ca_cert.ca_bundle }}
|
||||
{% else -%}
|
||||
verify: false
|
||||
{% endif -%}
|
||||
|
@ -40,6 +40,10 @@ requires:
|
||||
interface: ingress
|
||||
optional: true
|
||||
limit: 1
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
|
||||
provides:
|
||||
placement:
|
||||
interface: placement
|
||||
|
@ -95,6 +95,12 @@ class PlacementOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||
"root",
|
||||
self.service_group,
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
return _cconfigs
|
||||
|
||||
|
195
common.sh
195
common.sh
@ -77,6 +77,7 @@ EXTERNAL_AODH_LIBS=(
|
||||
"data_platform_libs"
|
||||
"rabbitmq_k8s"
|
||||
"traefik_k8s"
|
||||
"certificate_transfer_interface"
|
||||
)
|
||||
|
||||
EXTERNAL_BARBICAN_LIBS=(
|
||||
@ -84,10 +85,18 @@ EXTERNAL_BARBICAN_LIBS=(
|
||||
"rabbitmq_k8s"
|
||||
"traefik_k8s"
|
||||
"vault_k8s"
|
||||
"certificate_transfer_interface"
|
||||
)
|
||||
|
||||
EXTERNAL_CEILOMETER_LIBS=(
|
||||
"rabbitmq_k8s"
|
||||
"certificate_transfer_interface"
|
||||
)
|
||||
|
||||
EXTERNAL_CINDER_CEPH_LIBS=(
|
||||
"data_platform_libs"
|
||||
"rabbitmq_k8s"
|
||||
"traefik_k8s"
|
||||
)
|
||||
|
||||
EXTERNAL_DESIGNATE_BIND_LIBS=(
|
||||
@ -98,6 +107,7 @@ EXTERNAL_HEAT_LIBS=(
|
||||
"data_platform_libs"
|
||||
"rabbitmq_k8s"
|
||||
"traefik_route_k8s"
|
||||
"certificate_transfer_interface"
|
||||
)
|
||||
|
||||
EXTERNAL_NEUTRON_LIBS=(
|
||||
@ -105,18 +115,21 @@ EXTERNAL_NEUTRON_LIBS=(
|
||||
"rabbitmq_k8s"
|
||||
"traefik_k8s"
|
||||
"tls_certificates_interface"
|
||||
"certificate_transfer_interface"
|
||||
)
|
||||
|
||||
EXTERNAL_OCTAVIA_LIBS=(
|
||||
"data_platform_libs"
|
||||
"traefik_k8s"
|
||||
"tls_certificates_interface"
|
||||
"certificate_transfer_interface"
|
||||
)
|
||||
|
||||
EXTERNAL_OPENSTACK_EXPORTER_LIBS=(
|
||||
"grafana_k8s"
|
||||
"prometheus_k8s"
|
||||
"tls_certificates_interface"
|
||||
"certificate_transfer_interface"
|
||||
)
|
||||
|
||||
EXTERNAL_OPENSTACK_HYPERVISOR_LIBS=(
|
||||
@ -150,118 +163,134 @@ EXTERNAL_TEMPEST_LIBS=(
|
||||
|
||||
# Config template parts for each component.
|
||||
CONFIG_TEMPLATES_AODH=(
|
||||
"section-database"
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-service-credentials"
|
||||
"parts/section-database"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-service-credentials"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_BARBICAN=(
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-service-user"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-service-user"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_CEILOMETER=(
|
||||
"identity-data-id-creds"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-service-credentials-from-identity-service"
|
||||
"section-service-user-from-identity-credentials"
|
||||
"parts/identity-data-id-creds"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-service-credentials-from-identity-service"
|
||||
"parts/section-service-user-from-identity-credentials"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_CINDER=(
|
||||
"section-database"
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-service-user"
|
||||
"parts/section-database"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-service-user"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_CINDER_CEPH=(
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-oslo-notifications"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-oslo-notifications"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_DESIGNATE=(
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-service-user"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-service-user"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_GLANCE=(
|
||||
"section-database"
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-oslo-notifications"
|
||||
"section-service-user"
|
||||
"parts/section-database"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-oslo-notifications"
|
||||
"parts/section-service-user"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_GNOCCHI=(
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_HEAT=(
|
||||
"section-database"
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-trustee"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"parts/section-database"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-trustee"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_HORIZON=(
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_KEYSTONE=(
|
||||
"section-database"
|
||||
"database-connection"
|
||||
"section-federation"
|
||||
"section-middleware"
|
||||
"section-oslo-cache"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-oslo-middleware"
|
||||
"section-oslo-notifications"
|
||||
"section-signing"
|
||||
"parts/section-database"
|
||||
"parts/database-connection"
|
||||
"parts/section-federation"
|
||||
"parts/section-middleware"
|
||||
"parts/section-oslo-cache"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-oslo-middleware"
|
||||
"parts/section-oslo-notifications"
|
||||
"parts/section-signing"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_MAGNUM=(
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-service-user"
|
||||
"section-trust"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-service-user"
|
||||
"parts/section-trust"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_NEUTRON=(
|
||||
"section-database"
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-oslo-messaging-rabbit"
|
||||
"section-service-user"
|
||||
"parts/section-database"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-oslo-messaging-rabbit"
|
||||
"parts/section-service-user"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_NOVA=${CONFIG_TEMPLATES_NEUTRON[@]}
|
||||
|
||||
CONFIG_TEMPLATES_OCTAVIA=(
|
||||
"section-database"
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"parts/section-database"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
CONFIG_TEMPLATES_PLACEMENT=(
|
||||
"database-connection"
|
||||
"section-identity"
|
||||
"identity-data"
|
||||
"section-service-user"
|
||||
"parts/database-connection"
|
||||
"parts/section-identity"
|
||||
"parts/identity-data"
|
||||
"parts/section-service-user"
|
||||
"ca-bundle.pem.j2"
|
||||
)
|
||||
|
||||
declare -A INTERNAL_LIBS=(
|
||||
@ -297,7 +326,7 @@ declare -A EXTERNAL_LIBS=(
|
||||
[barbican-k8s]=${EXTERNAL_BARBICAN_LIBS[@]}
|
||||
[ceilometer-k8s]=${EXTERNAL_CEILOMETER_LIBS[@]}
|
||||
[cinder-k8s]=${EXTERNAL_AODH_LIBS[@]}
|
||||
[cinder-ceph-k8s]=${EXTERNAL_AODH_LIBS[@]}
|
||||
[cinder-ceph-k8s]=${EXTERNAL_CINDER_CEPH_LIBS[@]}
|
||||
[designate-k8s]=${EXTERNAL_AODH_LIBS[@]}
|
||||
[designate-bind-k8s]=${EXTERNAL_DESIGNATE_BIND_LIBS[@]}
|
||||
[glance-k8s]=${EXTERNAL_AODH_LIBS[@]}
|
||||
@ -331,14 +360,14 @@ declare -A CONFIG_TEMPLATES=(
|
||||
[glance-k8s]=${CONFIG_TEMPLATES_GLANCE[@]}
|
||||
[gnocchi-k8s]=${CONFIG_TEMPLATES_GNOCCHI[@]}
|
||||
[heat-k8s]=${CONFIG_TEMPLATES_HEAT[@]}
|
||||
[horizon-k8s]=${NULL_ARRAY[@]}
|
||||
[horizon-k8s]=${CONFIG_TEMPLATES_HORIZON[@]}
|
||||
[keystone-k8s]=${CONFIG_TEMPLATES_KEYSTONE[@]}
|
||||
[keystone-ldap-k8s]=${NULL_ARRAY[@]}
|
||||
[magnum-k8s]=${CONFIG_TEMPLATES_MAGNUM[@]}
|
||||
[neutron-k8s]=${CONFIG_TEMPLATES_NEUTRON[@]}
|
||||
[nova-k8s]=${CONFIG_TEMPLATES_NOVA[@]}
|
||||
[octavia-k8s]=${CONFIG_TEMPLATES_OCTAVIA[@]}
|
||||
[openstack-exporter-k8s]=${NULL_ARRAY[@]}
|
||||
[openstack-exporter-k8s]=${CONFIG_TEMPLATES_HORIZON[@]}
|
||||
[openstack-hypervisor]=${NULL_ARRAY[@]}
|
||||
[sunbeam-clusterd]=${NULL_ARRAY[@]}
|
||||
[sunbeam-machine]=${NULL_ARRAY[@]}
|
||||
@ -376,7 +405,7 @@ function copy_config_templates {
|
||||
config_templates_=${CONFIG_TEMPLATES[$1]}
|
||||
for part in ${config_templates_[@]}; do
|
||||
echo "Copying $part"
|
||||
cp -rf ../../templates/parts/$part src/templates/parts/
|
||||
cp -rf ../../templates/$part src/templates/$part
|
||||
done
|
||||
}
|
||||
|
||||
@ -392,6 +421,20 @@ function remove_libs {
|
||||
rm -rf lib
|
||||
}
|
||||
|
||||
function remove_config_templates {
|
||||
echo "remove_config_templates for $1:"
|
||||
config_templates_=${CONFIG_TEMPLATES[$1]}
|
||||
for part in ${config_templates_[@]}; do
|
||||
echo "Removing $part"
|
||||
rm src/templates/$part
|
||||
done
|
||||
|
||||
if (test -d src/templates/parts) && (test -n "$(find src/templates/parts -maxdepth 0 -empty)")
|
||||
then
|
||||
remove_templates_parts_dir
|
||||
fi
|
||||
}
|
||||
|
||||
function remove_templates_parts_dir {
|
||||
rm -rf src/templates/parts
|
||||
}
|
||||
@ -430,7 +473,7 @@ function pop_common_files {
|
||||
pushd charms/$1
|
||||
|
||||
remove_libs
|
||||
remove_templates_parts_dir
|
||||
remove_config_templates $1
|
||||
remove_stestr_conf
|
||||
remove_juju_ignore
|
||||
|
||||
|
390
libs/external/lib/charms/certificate_transfer_interface/v0/certificate_transfer.py
vendored
Normal file
390
libs/external/lib/charms/certificate_transfer_interface/v0/certificate_transfer.py
vendored
Normal file
@ -0,0 +1,390 @@
|
||||
# Copyright 2023 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
|
||||
"""Library for the certificate_transfer relation.
|
||||
|
||||
This library contains the Requires and Provides classes for handling the
|
||||
ertificate-transfer interface.
|
||||
|
||||
## Getting Started
|
||||
From a charm directory, fetch the library using `charmcraft`:
|
||||
|
||||
```shell
|
||||
charmcraft fetch-lib charms.certificate_transfer_interface.v0.certificate_transfer
|
||||
```
|
||||
|
||||
### Provider charm
|
||||
The provider charm is the charm providing public certificates to another charm that requires them.
|
||||
|
||||
Example:
|
||||
```python
|
||||
from ops.charm import CharmBase, RelationJoinedEvent
|
||||
from ops.main import main
|
||||
|
||||
from lib.charms.certificate_transfer_interface.v0.certificate_transfer import CertificateTransferProvides # noqa: E501 W505
|
||||
|
||||
|
||||
class DummyCertificateTransferProviderCharm(CharmBase):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.certificate_transfer = CertificateTransferProvides(self, "certificates")
|
||||
self.framework.observe(
|
||||
self.on.certificates_relation_joined, self._on_certificates_relation_joined
|
||||
)
|
||||
|
||||
def _on_certificates_relation_joined(self, event: RelationJoinedEvent):
|
||||
certificate = "my certificate"
|
||||
ca = "my CA certificate"
|
||||
chain = ["certificate 1", "certificate 2"]
|
||||
self.certificate_transfer.set_certificate(certificate=certificate, ca=ca, chain=chain, relation_id=event.relation.id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(DummyCertificateTransferProviderCharm)
|
||||
```
|
||||
|
||||
### Requirer charm
|
||||
The requirer charm is the charm requiring certificates from another charm that provides them.
|
||||
|
||||
Example:
|
||||
```python
|
||||
|
||||
from ops.charm import CharmBase
|
||||
from ops.main import main
|
||||
|
||||
from lib.charms.certificate_transfer_interface.v0.certificate_transfer import (
|
||||
CertificateAvailableEvent,
|
||||
CertificateRemovedEvent,
|
||||
CertificateTransferRequires,
|
||||
)
|
||||
|
||||
|
||||
class DummyCertificateTransferRequirerCharm(CharmBase):
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.certificate_transfer = CertificateTransferRequires(self, "certificates")
|
||||
self.framework.observe(
|
||||
self.certificate_transfer.on.certificate_available, self._on_certificate_available
|
||||
)
|
||||
self.framework.observe(
|
||||
self.certificate_transfer.on.certificate_removed, self._on_certificate_removed
|
||||
)
|
||||
|
||||
def _on_certificate_available(self, event: CertificateAvailableEvent):
|
||||
print(event.certificate)
|
||||
print(event.ca)
|
||||
print(event.chain)
|
||||
print(event.relation_id)
|
||||
|
||||
def _on_certificate_removed(self, event: CertificateRemovedEvent):
|
||||
print(event.relation_id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(DummyCertificateTransferRequirerCharm)
|
||||
```
|
||||
|
||||
You can relate both charms by running:
|
||||
|
||||
```bash
|
||||
juju relate <certificate_transfer provider charm> <certificate_transfer requirer charm>
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from jsonschema import exceptions, validate # type: ignore[import-untyped]
|
||||
from ops.charm import CharmBase, CharmEvents, RelationBrokenEvent, RelationChangedEvent
|
||||
from ops.framework import EventBase, EventSource, Handle, Object
|
||||
|
||||
# The unique Charmhub library identifier, never change it
|
||||
LIBID = "3785165b24a743f2b0c60de52db25c8b"
|
||||
|
||||
# Increment this major API version when introducing breaking changes
|
||||
LIBAPI = 0
|
||||
|
||||
# Increment this PATCH version before using `charmcraft publish-lib` or reset
|
||||
# to 0 if you are raising the major API version
|
||||
LIBPATCH = 5
|
||||
|
||||
PYDEPS = ["jsonschema"]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PROVIDER_JSON_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-07/schema",
|
||||
"$id": "https://canonical.github.io/charm-relation-interfaces/interfaces/certificate_transfer/schemas/provider.json", # noqa: E501
|
||||
"type": "object",
|
||||
"title": "`certificate_transfer` provider schema",
|
||||
"description": "The `certificate_transfer` root schema comprises the entire provider application databag for this interface.", # noqa: E501
|
||||
"default": {},
|
||||
"examples": [
|
||||
{
|
||||
"certificate": "-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIUW42TU9LSjEZLMCclWrvSwAsgRtcwDQYJKoZIhvcNAQEL\nBQAwIDELMAkGA1UEBhMCVVMxETAPBgNVBAMMCHdoYXRldmVyMB4XDTIzMDMyNDE4\nNDMxOVoXDTI0MDMyMzE4NDMxOVowPDELMAkGA1UEAwwCb2sxLTArBgNVBC0MJGUw\nNjVmMWI3LTE2OWEtNDE5YS1iNmQyLTc3OWJkOGM4NzIwNjCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAK42ixoklDH5K5i1NxXo/AFACDa956pE5RA57wlC\nBfgUYaIDRmv7TUVJh6zoMZSD6wjSZl3QgP7UTTZeHbvs3QE9HUwEkH1Lo3a8vD3z\neqsE2vSnOkpWWnPbfxiQyrTm77/LAWBt7lRLRLdfL6WcucD3wsGqm58sWXM3HG0f\nSN7PHCZUFqU6MpkHw8DiKmht5hBgWG+Vq3Zw8MNaqpwb/NgST3yYdcZwb58G2FTS\nZvDSdUfRmD/mY7TpciYV8EFylXNNFkth8oGNLunR9adgZ+9IunfRKj1a7S5GSwXU\nAZDaojw+8k5i3ikztsWH11wAVCiLj/3euIqq95z8xGycnKcCAwEAATANBgkqhkiG\n9w0BAQsFAAOCAQEAWMvcaozgBrZ/MAxzTJmp5gZyLxmMNV6iT9dcqbwzDtDtBvA/\n46ux6ytAQ+A7Bd3AubvozwCr1Id6g66ae0blWYRRZmF8fDdX/SBjIUkv7u9A3NVQ\nXN9gsEvK9pdpfN4ZiflfGSLdhM1STHycLmhG6H5s7HklbukMRhQi+ejbSzm/wiw1\nipcxuKhSUIVNkTLusN5b+HE2gwF1fn0K0z5jWABy08huLgbaEKXJEx5/FKLZGJga\nfpIzAdf25kMTu3gggseaAmzyX3AtT1i8A8nqYfe8fnnVMkvud89kq5jErv/hlMC9\n49g5yWQR2jilYYM3j9BHDuB+Rs+YS5BCep1JnQ==\n-----END CERTIFICATE-----\n", # noqa: E501
|
||||
"ca": "-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIUdiBwE/CtaBXJl3MArjZen6Y8kigwDQYJKoZIhvcNAQEL\nBQAwIDELMAkGA1UEBhMCVVMxETAPBgNVBAMMCHdoYXRldmVyMB4XDTIzMDMyNDE4\nNDg1OVoXDTI0MDMyMzE4NDg1OVowPDELMAkGA1UEAwwCb2sxLTArBgNVBC0MJDEw\nMDdjNDBhLWUwYzMtNDVlOS05YTAxLTVlYjY0NWQ0ZmEyZDCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBANOnUl6JDlXpLMRr/PxgtfE/E5Yk6E/TkPkPL/Kk\ntUGjEi42XZDg9zn3U6cjTDYu+rfKY2jiitfsduW6DQIkEpz3AvbuCMbbgnFpcjsB\nYysLSMTmuz/AVPrfnea/tQTALcONCSy1VhAjGSr81ZRSMB4khl9StSauZrbkpJ1P\nshqkFSUyAi31mKrnXz0Es/v0Yi0FzAlgWrZ4u1Ld+Bo2Xz7oK4mHf7/93Jc+tEaM\nIqG6ocD0q8bjPp0tlSxftVADNUzWlZfM6fue5EXzOsKqyDrxYOSchfU9dNzKsaBX\nkxbHEeSUPJeYYj7aVPEfAs/tlUGsoXQvwWfRie8grp2BoLECAwEAATANBgkqhkiG\n9w0BAQsFAAOCAQEACZARBpHYH6Gr2a1ka0mCWfBmOZqfDVan9rsI5TCThoylmaXW\nquEiZ2LObI+5faPzxSBhr9TjJlQamsd4ywout7pHKN8ZGqrCMRJ1jJbUfobu1n2k\nUOsY4+jzV1IRBXJzj64fLal4QhUNv341lAer6Vz3cAyRk7CK89b/DEY0x+jVpyZT\n1osx9JtsOmkDTgvdStGzq5kPKWOfjwHkmKQaZXliCgqbhzcCERppp1s/sX6K7nIh\n4lWiEmzUSD3Hngk51KGWlpZszO5KQ4cSZ3HUt/prg+tt0ROC3pY61k+m5dDUa9M8\nRtMI6iTjzSj/UV8DiAx0yeM+bKoy4jGeXmaL3g==\n-----END CERTIFICATE-----\n", # noqa: E501
|
||||
"chain": [
|
||||
"-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIUW42TU9LSjEZLMCclWrvSwAsgRtcwDQYJKoZIhvcNAQEL\nBQAwIDELMAkGA1UEBhMCVVMxETAPBgNVBAMMCHdoYXRldmVyMB4XDTIzMDMyNDE4\nNDMxOVoXDTI0MDMyMzE4NDMxOVowPDELMAkGA1UEAwwCb2sxLTArBgNVBC0MJGUw\nNjVmMWI3LTE2OWEtNDE5YS1iNmQyLTc3OWJkOGM4NzIwNjCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBAK42ixoklDH5K5i1NxXo/AFACDa956pE5RA57wlC\nBfgUYaIDRmv7TUVJh6zoMZSD6wjSZl3QgP7UTTZeHbvs3QE9HUwEkH1Lo3a8vD3z\neqsE2vSnOkpWWnPbfxiQyrTm77/LAWBt7lRLRLdfL6WcucD3wsGqm58sWXM3HG0f\nSN7PHCZUFqU6MpkHw8DiKmht5hBgWG+Vq3Zw8MNaqpwb/NgST3yYdcZwb58G2FTS\nZvDSdUfRmD/mY7TpciYV8EFylXNNFkth8oGNLunR9adgZ+9IunfRKj1a7S5GSwXU\nAZDaojw+8k5i3ikztsWH11wAVCiLj/3euIqq95z8xGycnKcCAwEAATANBgkqhkiG\n9w0BAQsFAAOCAQEAWMvcaozgBrZ/MAxzTJmp5gZyLxmMNV6iT9dcqbwzDtDtBvA/\n46ux6ytAQ+A7Bd3AubvozwCr1Id6g66ae0blWYRRZmF8fDdX/SBjIUkv7u9A3NVQ\nXN9gsEvK9pdpfN4ZiflfGSLdhM1STHycLmhG6H5s7HklbukMRhQi+ejbSzm/wiw1\nipcxuKhSUIVNkTLusN5b+HE2gwF1fn0K0z5jWABy08huLgbaEKXJEx5/FKLZGJga\nfpIzAdf25kMTu3gggseaAmzyX3AtT1i8A8nqYfe8fnnVMkvud89kq5jErv/hlMC9\n49g5yWQR2jilYYM3j9BHDuB+Rs+YS5BCep1JnQ==\n-----END CERTIFICATE-----\n", # noqa: E501
|
||||
"-----BEGIN CERTIFICATE-----\nMIIC6DCCAdCgAwIBAgIUdiBwE/CtaBXJl3MArjZen6Y8kigwDQYJKoZIhvcNAQEL\nBQAwIDELMAkGA1UEBhMCVVMxETAPBgNVBAMMCHdoYXRldmVyMB4XDTIzMDMyNDE4\nNDg1OVoXDTI0MDMyMzE4NDg1OVowPDELMAkGA1UEAwwCb2sxLTArBgNVBC0MJDEw\nMDdjNDBhLWUwYzMtNDVlOS05YTAxLTVlYjY0NWQ0ZmEyZDCCASIwDQYJKoZIhvcN\nAQEBBQADggEPADCCAQoCggEBANOnUl6JDlXpLMRr/PxgtfE/E5Yk6E/TkPkPL/Kk\ntUGjEi42XZDg9zn3U6cjTDYu+rfKY2jiitfsduW6DQIkEpz3AvbuCMbbgnFpcjsB\nYysLSMTmuz/AVPrfnea/tQTALcONCSy1VhAjGSr81ZRSMB4khl9StSauZrbkpJ1P\nshqkFSUyAi31mKrnXz0Es/v0Yi0FzAlgWrZ4u1Ld+Bo2Xz7oK4mHf7/93Jc+tEaM\nIqG6ocD0q8bjPp0tlSxftVADNUzWlZfM6fue5EXzOsKqyDrxYOSchfU9dNzKsaBX\nkxbHEeSUPJeYYj7aVPEfAs/tlUGsoXQvwWfRie8grp2BoLECAwEAATANBgkqhkiG\n9w0BAQsFAAOCAQEACZARBpHYH6Gr2a1ka0mCWfBmOZqfDVan9rsI5TCThoylmaXW\nquEiZ2LObI+5faPzxSBhr9TjJlQamsd4ywout7pHKN8ZGqrCMRJ1jJbUfobu1n2k\nUOsY4+jzV1IRBXJzj64fLal4QhUNv341lAer6Vz3cAyRk7CK89b/DEY0x+jVpyZT\n1osx9JtsOmkDTgvdStGzq5kPKWOfjwHkmKQaZXliCgqbhzcCERppp1s/sX6K7nIh\n4lWiEmzUSD3Hngk51KGWlpZszO5KQ4cSZ3HUt/prg+tt0ROC3pY61k+m5dDUa9M8\nRtMI6iTjzSj/UV8DiAx0yeM+bKoy4jGeXmaL3g==\n-----END CERTIFICATE-----\n", # noqa: E501
|
||||
],
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"certificate": {
|
||||
"$id": "#/properties/certificate",
|
||||
"type": "string",
|
||||
"title": "Public TLS certificate",
|
||||
"description": "Public TLS certificate",
|
||||
},
|
||||
"ca": {
|
||||
"$id": "#/properties/ca",
|
||||
"type": "string",
|
||||
"title": "CA public TLS certificate",
|
||||
"description": "CA Public TLS certificate",
|
||||
},
|
||||
"chain": {
|
||||
"$id": "#/properties/chain",
|
||||
"type": "array",
|
||||
"items": {"type": "string", "$id": "#/properties/chain/items"},
|
||||
"title": "CA public TLS certificate chain",
|
||||
"description": "CA public TLS certificate chain",
|
||||
},
|
||||
},
|
||||
"anyOf": [{"required": ["certificate"]}, {"required": ["ca"]}, {"required": ["chain"]}],
|
||||
"additionalProperties": True,
|
||||
}
|
||||
|
||||
|
||||
class CertificateAvailableEvent(EventBase):
|
||||
"""Charm Event triggered when a TLS certificate is available."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
handle: Handle,
|
||||
certificate: str,
|
||||
ca: str,
|
||||
chain: List[str],
|
||||
relation_id: int,
|
||||
):
|
||||
super().__init__(handle)
|
||||
self.certificate = certificate
|
||||
self.ca = ca
|
||||
self.chain = chain
|
||||
self.relation_id = relation_id
|
||||
|
||||
def snapshot(self) -> dict:
|
||||
"""Return snapshot."""
|
||||
return {
|
||||
"certificate": self.certificate,
|
||||
"ca": self.ca,
|
||||
"chain": self.chain,
|
||||
"relation_id": self.relation_id,
|
||||
}
|
||||
|
||||
def restore(self, snapshot: dict):
|
||||
"""Restores snapshot."""
|
||||
self.certificate = snapshot["certificate"]
|
||||
self.ca = snapshot["ca"]
|
||||
self.chain = snapshot["chain"]
|
||||
self.relation_id = snapshot["relation_id"]
|
||||
|
||||
|
||||
class CertificateRemovedEvent(EventBase):
|
||||
"""Charm Event triggered when a TLS certificate is removed."""
|
||||
|
||||
def __init__(self, handle: Handle, relation_id: int):
|
||||
super().__init__(handle)
|
||||
self.relation_id = relation_id
|
||||
|
||||
def snapshot(self) -> dict:
|
||||
"""Return snapshot."""
|
||||
return {"relation_id": self.relation_id}
|
||||
|
||||
def restore(self, snapshot: dict):
|
||||
"""Restores snapshot."""
|
||||
self.relation_id = snapshot["relation_id"]
|
||||
|
||||
|
||||
def _load_relation_data(raw_relation_data: dict) -> dict:
|
||||
"""Load relation data from the relation data bag.
|
||||
|
||||
Args:
|
||||
raw_relation_data: Relation data from the databag
|
||||
|
||||
Returns:
|
||||
dict: Relation data in dict format.
|
||||
"""
|
||||
loaded_relation_data = {}
|
||||
for key in raw_relation_data:
|
||||
try:
|
||||
loaded_relation_data[key] = json.loads(raw_relation_data[key])
|
||||
except (json.decoder.JSONDecodeError, TypeError):
|
||||
loaded_relation_data[key] = raw_relation_data[key]
|
||||
return loaded_relation_data
|
||||
|
||||
|
||||
class CertificateTransferRequirerCharmEvents(CharmEvents):
|
||||
"""List of events that the Certificate Transfer requirer charm can leverage."""
|
||||
|
||||
certificate_available = EventSource(CertificateAvailableEvent)
|
||||
certificate_removed = EventSource(CertificateRemovedEvent)
|
||||
|
||||
|
||||
class CertificateTransferProvides(Object):
|
||||
"""Certificate Transfer provider class."""
|
||||
|
||||
def __init__(self, charm: CharmBase, relationship_name: str):
|
||||
super().__init__(charm, relationship_name)
|
||||
self.charm = charm
|
||||
self.relationship_name = relationship_name
|
||||
|
||||
def set_certificate(
|
||||
self,
|
||||
certificate: str,
|
||||
ca: str,
|
||||
chain: List[str],
|
||||
relation_id: int,
|
||||
) -> None:
|
||||
"""Add certificates to relation data.
|
||||
|
||||
Args:
|
||||
certificate (str): Certificate
|
||||
ca (str): CA Certificate
|
||||
chain (list): CA Chain
|
||||
relation_id (int): Juju relation ID
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
relation = self.model.get_relation(
|
||||
relation_name=self.relationship_name,
|
||||
relation_id=relation_id,
|
||||
)
|
||||
if not relation:
|
||||
raise RuntimeError(
|
||||
f"No relation found with relation name {self.relationship_name} and "
|
||||
f"relation ID {relation_id}"
|
||||
)
|
||||
relation.data[self.model.unit]["certificate"] = certificate
|
||||
relation.data[self.model.unit]["ca"] = ca
|
||||
relation.data[self.model.unit]["chain"] = json.dumps(chain)
|
||||
|
||||
def remove_certificate(self, relation_id: int) -> None:
|
||||
"""Remove a given certificate from relation data.
|
||||
|
||||
Args:
|
||||
relation_id (int): Relation ID
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
relation = self.model.get_relation(
|
||||
relation_name=self.relationship_name,
|
||||
relation_id=relation_id,
|
||||
)
|
||||
if not relation:
|
||||
logger.warning(
|
||||
f"Can't remove certificate - Non-existent relation '{self.relationship_name}'"
|
||||
)
|
||||
return
|
||||
unit_relation_data = relation.data[self.model.unit]
|
||||
certificate_removed = False
|
||||
if "certificate" in unit_relation_data:
|
||||
relation.data[self.model.unit].pop("certificate")
|
||||
certificate_removed = True
|
||||
if "ca" in unit_relation_data:
|
||||
relation.data[self.model.unit].pop("ca")
|
||||
certificate_removed = True
|
||||
if "chain" in unit_relation_data:
|
||||
relation.data[self.model.unit].pop("chain")
|
||||
certificate_removed = True
|
||||
|
||||
if certificate_removed:
|
||||
logger.warning("Certificate removed from relation data")
|
||||
else:
|
||||
logger.warning("Can't remove certificate - No certificate in relation data")
|
||||
|
||||
|
||||
class CertificateTransferRequires(Object):
|
||||
"""TLS certificates requirer class to be instantiated by TLS certificates requirers."""
|
||||
|
||||
on = CertificateTransferRequirerCharmEvents()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
charm: CharmBase,
|
||||
relationship_name: str,
|
||||
):
|
||||
"""Generates/use private key and observes relation changed event.
|
||||
|
||||
Args:
|
||||
charm: Charm object
|
||||
relationship_name: Juju relation name
|
||||
"""
|
||||
super().__init__(charm, relationship_name)
|
||||
self.relationship_name = relationship_name
|
||||
self.charm = charm
|
||||
self.framework.observe(
|
||||
charm.on[relationship_name].relation_changed, self._on_relation_changed
|
||||
)
|
||||
self.framework.observe(
|
||||
charm.on[relationship_name].relation_broken, self._on_relation_broken
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _relation_data_is_valid(relation_data: dict) -> bool:
|
||||
"""Return whether relation data is valid based on json schema.
|
||||
|
||||
Args:
|
||||
relation_data: Relation data in dict format.
|
||||
|
||||
Returns:
|
||||
bool: Whether relation data is valid.
|
||||
"""
|
||||
try:
|
||||
validate(instance=relation_data, schema=PROVIDER_JSON_SCHEMA)
|
||||
return True
|
||||
except exceptions.ValidationError:
|
||||
return False
|
||||
|
||||
def _on_relation_changed(self, event: RelationChangedEvent) -> None:
|
||||
"""Emit certificate available event.
|
||||
|
||||
Args:
|
||||
event: Juju event
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
if not event.unit:
|
||||
logger.info(f"No remote unit in relation: {self.relationship_name}")
|
||||
return
|
||||
remote_unit_relation_data = _load_relation_data(event.relation.data[event.unit])
|
||||
if not self._relation_data_is_valid(remote_unit_relation_data):
|
||||
logger.warning(
|
||||
f"Provider relation data did not pass JSON Schema validation: "
|
||||
f"{event.relation.data[event.unit]}"
|
||||
)
|
||||
return
|
||||
self.on.certificate_available.emit(
|
||||
certificate=remote_unit_relation_data.get("certificate"),
|
||||
ca=remote_unit_relation_data.get("ca"),
|
||||
chain=remote_unit_relation_data.get("chain"),
|
||||
relation_id=event.relation.id,
|
||||
)
|
||||
|
||||
def _on_relation_broken(self, event: RelationBrokenEvent) -> None:
|
||||
"""Handler triggered on relation broken event.
|
||||
|
||||
Args:
|
||||
event: Juju event
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
self.on.certificate_removed.emit(relation_id=event.relation.id)
|
@ -540,6 +540,21 @@ class OSBaseOperatorCharmK8S(OSBaseOperatorCharm):
|
||||
super().__init__(framework)
|
||||
self.pebble_handlers = self.get_pebble_handlers()
|
||||
|
||||
def get_relation_handlers(
|
||||
self, handlers: List[sunbeam_rhandlers.RelationHandler] = None
|
||||
) -> List[sunbeam_rhandlers.RelationHandler]:
|
||||
"""Relation handlers for the service."""
|
||||
handlers = handlers or []
|
||||
if self.can_add_handler("receive-ca-cert", handlers):
|
||||
self.receive_ca_cert = (
|
||||
sunbeam_rhandlers.CertificateTransferRequiresHandler(
|
||||
self, "receive-ca-cert", self.configure_charm
|
||||
)
|
||||
)
|
||||
handlers.append(self.receive_ca_cert)
|
||||
|
||||
return super().get_relation_handlers(handlers)
|
||||
|
||||
def get_pebble_handlers(self) -> List[sunbeam_chandlers.PebbleHandler]:
|
||||
"""Pebble handlers for the operator."""
|
||||
return [
|
||||
|
@ -40,6 +40,7 @@ from ops.model import (
|
||||
ActiveStatus,
|
||||
BlockedStatus,
|
||||
SecretNotFoundError,
|
||||
Unit,
|
||||
UnknownStatus,
|
||||
WaitingStatus,
|
||||
)
|
||||
@ -1781,3 +1782,84 @@ class UserIdentityResourceRequiresHandler(RelationHandler):
|
||||
def ready(self) -> bool:
|
||||
"""Whether the relation is ready."""
|
||||
return self.get_config_credentials() is not None
|
||||
|
||||
|
||||
class CertificateTransferRequiresHandler(RelationHandler):
|
||||
"""Handle certificate transfer relation on the requires side."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
charm: ops.charm.CharmBase,
|
||||
relation_name: str,
|
||||
callback_f: Callable,
|
||||
mandatory: bool = False,
|
||||
):
|
||||
"""Create a new certificate-transfer requires handler.
|
||||
|
||||
Create a new CertificateTransferRequiresHandler that receives the
|
||||
certificates from the provider and updates certificates on all
|
||||
the containers.
|
||||
|
||||
:param charm: the Charm class the handler is for
|
||||
:type charm: ops.charm.CharmBase
|
||||
:param relation_name: the relation the handler is bound to
|
||||
:type relation_name: str
|
||||
:param callback_f: the function to call when the nodes are connected
|
||||
:type callback_f: Callable
|
||||
:param mandatory: If the relation is mandatory to proceed with
|
||||
configuring charm
|
||||
:type mandatory: bool
|
||||
"""
|
||||
super().__init__(charm, relation_name, callback_f, mandatory)
|
||||
|
||||
def setup_event_handler(self) -> None:
|
||||
"""Configure event handlers for tls relation."""
|
||||
logger.debug("Setting up certificate transfer event handler")
|
||||
|
||||
from charms.certificate_transfer_interface.v0.certificate_transfer import (
|
||||
CertificateTransferRequires,
|
||||
)
|
||||
|
||||
recv_ca_cert = CertificateTransferRequires(
|
||||
self.charm, "receive-ca-cert"
|
||||
)
|
||||
self.framework.observe(
|
||||
recv_ca_cert.on.certificate_available,
|
||||
self._on_recv_ca_cert_available,
|
||||
)
|
||||
self.framework.observe(
|
||||
recv_ca_cert.on.certificate_removed, self._on_recv_ca_cert_removed
|
||||
)
|
||||
return recv_ca_cert
|
||||
|
||||
def _on_recv_ca_cert_available(self, event: ops.framework.EventBase):
|
||||
self.callback_f(event)
|
||||
|
||||
def _on_recv_ca_cert_removed(self, event: ops.framework.EventBase):
|
||||
self.callback_f(event)
|
||||
|
||||
@property
|
||||
def ready(self) -> bool:
|
||||
"""Check if relation handler is ready."""
|
||||
return True
|
||||
|
||||
def context(self) -> dict:
|
||||
"""Context containing ca cert data."""
|
||||
receive_ca_cert_relations = list(
|
||||
self.model.relations[self.relation_name]
|
||||
)
|
||||
if not receive_ca_cert_relations:
|
||||
return {}
|
||||
|
||||
ca_bundle = []
|
||||
for k, v in receive_ca_cert_relations[0].data.items():
|
||||
if isinstance(k, Unit) and k != self.model.unit:
|
||||
ca = v.get("ca")
|
||||
chain = json.loads(v.get("chain", "[]"))
|
||||
if ca and ca not in ca_bundle:
|
||||
ca_bundle.append(ca)
|
||||
for chain_ in chain:
|
||||
if chain_ not in ca_bundle:
|
||||
ca_bundle.append(chain_)
|
||||
|
||||
return {"ca_bundle": "\n".join(ca_bundle)}
|
||||
|
3
templates/ca-bundle.pem.j2
Normal file
3
templates/ca-bundle.pem.j2
Normal file
@ -0,0 +1,3 @@
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
{{ receive_ca_cert.ca_bundle }}
|
||||
{% endif %}
|
@ -1,9 +1,9 @@
|
||||
{% if identity_service.admin_auth_url -%}
|
||||
auth_url = {{ identity_service.admin_auth_url }}
|
||||
interface = admin
|
||||
{% elif identity_service.internal_auth_url -%}
|
||||
{% if identity_service.internal_auth_url -%}
|
||||
auth_url = {{ identity_service.internal_auth_url }}
|
||||
interface = internal
|
||||
{% elif identity_service.admin_auth_url -%}
|
||||
auth_url = {{ identity_service.admin_auth_url }}
|
||||
interface = admin
|
||||
{% elif identity_service.internal_host -%}
|
||||
auth_url = {{ identity_service.internal_protocol }}://{{ identity_service.internal_host }}:{{ identity_service.internal_port }}
|
||||
interface = internal
|
||||
@ -19,5 +19,8 @@ user_domain_name = {{ identity_service.service_domain_name }}
|
||||
project_name = {{ identity_service.service_project_name }}
|
||||
username = {{ identity_service.service_user_name }}
|
||||
password = {{ identity_service.service_password }}
|
||||
{% if receive_ca_cert and receive_ca_cert.ca_bundle -%}
|
||||
cafile = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
service_token_roles = {{ identity_service.admin_role }}
|
||||
service_token_roles_required = True
|
||||
|
@ -19,5 +19,8 @@ user_domain_name = {{ identity_credentials.user_domain_name }}
|
||||
project_name = {{ identity_credentials.project_name }}
|
||||
username = {{ identity_credentials.username }}
|
||||
password = {{ identity_credentials.password }}
|
||||
{% if receive_ca_cert and receive_ca-cert.ca_bundle -%}
|
||||
cafile = /usr/local/share/ca-certificates/ca-bundle.pem
|
||||
{% endif -%}
|
||||
service_token_roles = {{ identity_credentials.admin_role }}
|
||||
service_token_roles_required = True
|
||||
|
@ -141,6 +141,8 @@ relations:
|
||||
- glance:amqp
|
||||
- - traefik:ingress
|
||||
- glance:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- glance:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- heat:database
|
||||
@ -152,6 +154,8 @@ relations:
|
||||
- heat:traefik-route-public
|
||||
- - rabbitmq:amqp
|
||||
- heat:amqp
|
||||
- - keystone:send-ca-cert
|
||||
- heat:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- octavia:database
|
||||
@ -165,6 +169,8 @@ relations:
|
||||
- octavia:certificates
|
||||
- - octavia:ovsdb-cms
|
||||
- ovn-central:ovsdb-cms
|
||||
- - keystone:send-ca-cert
|
||||
- octavia:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- barbican:database
|
||||
@ -178,6 +184,8 @@ relations:
|
||||
- barbican:ingress-public
|
||||
- - vault:vault-kv
|
||||
- barbican:vault-kv
|
||||
- - keystone:send-ca-cert
|
||||
- barbican:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- magnum:database
|
||||
@ -189,3 +197,5 @@ relations:
|
||||
- magnum:identity-ops
|
||||
- - traefik:ingress
|
||||
- magnum:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- magnum:receive-ca-cert
|
||||
|
@ -114,6 +114,8 @@ relations:
|
||||
- cinder:identity-service
|
||||
- - traefik:ingress
|
||||
- cinder:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- cinder:receive-ca-cert
|
||||
|
||||
- - cinder-ceph:database
|
||||
- mysql:database
|
||||
@ -128,6 +130,8 @@ relations:
|
||||
- gnocchi:ingress-public
|
||||
- - keystone:identity-service
|
||||
- gnocchi:identity-service
|
||||
- - keystone:send-ca-cert
|
||||
- gnocchi:receive-ca-cert
|
||||
|
||||
- - rabbitmq:amqp
|
||||
- ceilometer:amqp
|
||||
@ -135,6 +139,8 @@ relations:
|
||||
- ceilometer:identity-credentials
|
||||
- - gnocchi:gnocchi-service
|
||||
- ceilometer:gnocchi-db
|
||||
- - keystone:send-ca-cert
|
||||
- ceilometer:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- aodh:database
|
||||
@ -144,3 +150,5 @@ relations:
|
||||
- aodh:identity-service
|
||||
- - traefik:ingress
|
||||
- aodh:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- aodh:receive-ca-cert
|
||||
|
@ -151,6 +151,8 @@ relations:
|
||||
- glance:amqp
|
||||
- - traefik:ingress
|
||||
- glance:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- glance:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- nova:database
|
||||
@ -164,6 +166,8 @@ relations:
|
||||
- nova:identity-service
|
||||
- - traefik:ingress
|
||||
- nova:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- nova:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- placement:database
|
||||
@ -171,6 +175,8 @@ relations:
|
||||
- placement:identity-service
|
||||
- - traefik:ingress
|
||||
- placement:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- placement:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- neutron:database
|
||||
@ -184,6 +190,8 @@ relations:
|
||||
- neutron:certificates
|
||||
- - neutron:ovsdb-cms
|
||||
- ovn-central:ovsdb-cms
|
||||
- - keystone:send-ca-cert
|
||||
- neutron:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- horizon:database
|
||||
@ -191,3 +199,5 @@ relations:
|
||||
- horizon:identity-credentials
|
||||
- - traefik:ingress
|
||||
- horizon:ingress-public
|
||||
- - keystone:send-ca-cert
|
||||
- horizon:receive-ca-cert
|
||||
|
@ -98,6 +98,8 @@ relations:
|
||||
- designate:ingress-public
|
||||
- - designate-bind:dns-backend
|
||||
- designate:dns-backend
|
||||
- - keystone:send-ca-cert
|
||||
- designate:receive-ca-cert
|
||||
|
||||
- - keystone:domain-config
|
||||
- keystone-ldap:domain-config
|
||||
|
Loading…
x
Reference in New Issue
Block a user