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
|
limit: 1
|
||||||
amqp:
|
amqp:
|
||||||
interface: rabbitmq
|
interface: rabbitmq
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
aodh:
|
aodh:
|
||||||
|
@ -93,7 +93,13 @@ class AODHEvaluatorPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
|||||||
"root",
|
"root",
|
||||||
"aodh",
|
"aodh",
|
||||||
0o640,
|
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",
|
"root",
|
||||||
"aodh",
|
"aodh",
|
||||||
0o640,
|
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",
|
"root",
|
||||||
"aodh",
|
"aodh",
|
||||||
0o640,
|
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",
|
"root",
|
||||||
"aodh",
|
"aodh",
|
||||||
0o640,
|
0o640,
|
||||||
)
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
"aodh",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,6 +42,9 @@ requires:
|
|||||||
vault-kv:
|
vault-kv:
|
||||||
interface: vault-kv
|
interface: vault-kv
|
||||||
limit: 1
|
limit: 1
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -222,7 +222,13 @@ class BarbicanWorkerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
|||||||
return [
|
return [
|
||||||
sunbeam_core.ContainerConfigFile(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/etc/barbican/barbican.conf", "barbican", "barbican"
|
"/etc/barbican/barbican.conf", "barbican", "barbican"
|
||||||
)
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
"barbican",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -464,10 +470,18 @@ class BarbicanVaultOperatorCharm(BarbicanOperatorCharm):
|
|||||||
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
|
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
|
||||||
"""Container configuration files for the service."""
|
"""Container configuration files for the service."""
|
||||||
_cconfigs = super().container_configs
|
_cconfigs = super().container_configs
|
||||||
_cconfigs.append(
|
_cconfigs.extend(
|
||||||
sunbeam_core.ContainerConfigFile(
|
[
|
||||||
self.ca_crt_file, "barbican", "barbican"
|
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
|
return _cconfigs
|
||||||
|
|
||||||
|
@ -49,6 +49,9 @@ requires:
|
|||||||
limit: 1
|
limit: 1
|
||||||
gnocchi-db:
|
gnocchi-db:
|
||||||
interface: gnocchi
|
interface: gnocchi
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -305,6 +305,12 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
|
|||||||
self.service_group,
|
self.service_group,
|
||||||
0o640,
|
0o640,
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
self.service_group,
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
return _cconfigs
|
return _cconfigs
|
||||||
|
|
||||||
|
@ -57,6 +57,9 @@ requires:
|
|||||||
image-service:
|
image-service:
|
||||||
interface: glance
|
interface: glance
|
||||||
optional: true
|
optional: true
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -92,6 +92,12 @@ class CinderWSGIPebbleHandler(sunbeam_chandlers.WSGIPebbleHandler):
|
|||||||
sunbeam_core.ContainerConfigFile(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/etc/cinder/cinder.conf", "root", "cinder", 0o640
|
"/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 [
|
return [
|
||||||
sunbeam_core.ContainerConfigFile(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/etc/cinder/cinder.conf", "root", "cinder", 0o640
|
"/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:
|
dns-backend:
|
||||||
interface: bind-rndc
|
interface: bind-rndc
|
||||||
limit: 1
|
limit: 1
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -144,6 +144,12 @@ class DesignatePebbleHandler(sunbeam_chandlers.WSGIPebbleHandler):
|
|||||||
"designate",
|
"designate",
|
||||||
"designate",
|
"designate",
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
"designate",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return _cconfig
|
return _cconfig
|
||||||
|
@ -67,6 +67,9 @@ requires:
|
|||||||
ceph:
|
ceph:
|
||||||
interface: ceph-client
|
interface: ceph-client
|
||||||
optional: true
|
optional: true
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
image-service:
|
image-service:
|
||||||
|
@ -251,6 +251,12 @@ class GlanceOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
self.service_group,
|
self.service_group,
|
||||||
0o640,
|
0o640,
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
self.service_group,
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
if self.has_ceph_relation():
|
if self.has_ceph_relation():
|
||||||
_cconfigs.extend(
|
_cconfigs.extend(
|
||||||
|
@ -51,6 +51,9 @@ requires:
|
|||||||
limit: 1
|
limit: 1
|
||||||
ceph:
|
ceph:
|
||||||
interface: ceph-client
|
interface: ceph-client
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
gnocchi-service:
|
gnocchi-service:
|
||||||
|
@ -280,6 +280,12 @@ class GnocchiOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
self.service_group,
|
self.service_group,
|
||||||
0o640,
|
0o640,
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
self.service_group,
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def configure_app_leader(self, event: EventBase):
|
def configure_app_leader(self, event: EventBase):
|
||||||
|
@ -54,6 +54,9 @@ requires:
|
|||||||
interface: rabbitmq
|
interface: rabbitmq
|
||||||
identity-ops:
|
identity-ops:
|
||||||
interface: keystone-resources
|
interface: keystone-resources
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -426,6 +426,7 @@ class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
"rule": f"PathPrefix(`/{model}-{app}`)",
|
"rule": f"PathPrefix(`/{model}-{app}`)",
|
||||||
"service": f"juju-{model}-{app}-service",
|
"service": f"juju-{model}-{app}-service",
|
||||||
"entryPoints": ["websecure"],
|
"entryPoints": ["websecure"],
|
||||||
|
"tls": {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -677,6 +678,12 @@ class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
self.service_group,
|
self.service_group,
|
||||||
0o640,
|
0o640,
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
self.service_group,
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def heat_api_cfn_container_configs(self):
|
def heat_api_cfn_container_configs(self):
|
||||||
@ -694,6 +701,12 @@ class HeatOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
self.service_group,
|
self.service_group,
|
||||||
0o640,
|
0o640,
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
self.service_group,
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
def _get_create_role_ops(self) -> list:
|
def _get_create_role_ops(self) -> list:
|
||||||
|
@ -43,6 +43,9 @@ requires:
|
|||||||
identity-credentials:
|
identity-credentials:
|
||||||
interface: keystone-credentials
|
interface: keystone-credentials
|
||||||
limit: 1
|
limit: 1
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
horizon:
|
horizon:
|
||||||
|
@ -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]:
|
def get_pebble_handlers(self) -> List[sunbeam_chandlers.PebbleHandler]:
|
||||||
"""Pebble handlers for the service."""
|
"""Pebble handlers for the service."""
|
||||||
return [
|
return [
|
||||||
|
@ -198,6 +198,9 @@ OPENSTACK_KEYSTONE_URL = "{{ identity_credentials.internal_protocol }}://%s:{{ i
|
|||||||
OPENSTACK_API_VERSIONS = { "identity": 3, }
|
OPENSTACK_API_VERSIONS = { "identity": 3, }
|
||||||
OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True
|
OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True
|
||||||
OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "{{ options.default_domain or identity_credentials.project_domain_id }}"
|
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.
|
# Enables keystone web single-sign-on if set to True.
|
||||||
#WEBSSO_ENABLED = False
|
#WEBSSO_ENABLED = False
|
||||||
|
@ -27,3 +27,34 @@ regenerate-password:
|
|||||||
required:
|
required:
|
||||||
- username
|
- username
|
||||||
additionalProperties: False
|
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
|
interface: keystone-credentials
|
||||||
identity-ops:
|
identity-ops:
|
||||||
interface: keystone-resources
|
interface: keystone-resources
|
||||||
|
send-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
|
||||||
requires:
|
requires:
|
||||||
database:
|
database:
|
||||||
|
@ -25,6 +25,8 @@ develop a new k8s charm using the Operator Framework:
|
|||||||
https://discourse.charmhub.io/t/4208
|
https://discourse.charmhub.io/t/4208
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import binascii
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from collections import (
|
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.job_ctrl as sunbeam_job_ctrl
|
||||||
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
|
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
|
||||||
import pwgen
|
import pwgen
|
||||||
|
from charms.certificate_transfer_interface.v0.certificate_transfer import (
|
||||||
|
CertificateTransferProvides,
|
||||||
|
)
|
||||||
from ops.charm import (
|
from ops.charm import (
|
||||||
ActionEvent,
|
ActionEvent,
|
||||||
RelationChangedEvent,
|
RelationChangedEvent,
|
||||||
@ -68,10 +73,12 @@ from ops.model import (
|
|||||||
ActiveStatus,
|
ActiveStatus,
|
||||||
MaintenanceStatus,
|
MaintenanceStatus,
|
||||||
ModelError,
|
ModelError,
|
||||||
|
Relation,
|
||||||
SecretNotFoundError,
|
SecretNotFoundError,
|
||||||
SecretRotate,
|
SecretRotate,
|
||||||
)
|
)
|
||||||
from utils import (
|
from utils import (
|
||||||
|
certs,
|
||||||
manager,
|
manager,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -81,8 +88,7 @@ KEYSTONE_CONTAINER = "keystone"
|
|||||||
FERNET_KEYS_PREFIX = "fernet-"
|
FERNET_KEYS_PREFIX = "fernet-"
|
||||||
CREDENTIALS_SECRET_PREFIX = "credentials_"
|
CREDENTIALS_SECRET_PREFIX = "credentials_"
|
||||||
SECRET_PREFIX = "secret://"
|
SECRET_PREFIX = "secret://"
|
||||||
|
CERTIFICATE_TRANSFER_LABEL = "certs_to_transfer"
|
||||||
|
|
||||||
KEYSTONE_CONF = "/etc/keystone/keystone.conf"
|
KEYSTONE_CONF = "/etc/keystone/keystone.conf"
|
||||||
LOGGING_CONF = "/etc/keystone/logging.conf"
|
LOGGING_CONF = "/etc/keystone/logging.conf"
|
||||||
|
|
||||||
@ -333,6 +339,7 @@ class KeystoneOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
IDSVC_RELATION_NAME = "identity-service"
|
IDSVC_RELATION_NAME = "identity-service"
|
||||||
IDCREDS_RELATION_NAME = "identity-credentials"
|
IDCREDS_RELATION_NAME = "identity-credentials"
|
||||||
IDOPS_RELATION_NAME = "identity-ops"
|
IDOPS_RELATION_NAME = "identity-ops"
|
||||||
|
SEND_CA_CERT_RELATION_NAME = "send-ca-cert"
|
||||||
|
|
||||||
def __init__(self, framework):
|
def __init__(self, framework):
|
||||||
super().__init__(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(default_domain_id=None)
|
||||||
self._state.set_default(service_project_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.framework.observe(
|
||||||
self.on.peers_relation_changed, self._on_peer_data_changed
|
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.framework.observe(
|
||||||
self.on.get_admin_password_action, self._get_admin_password_action
|
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.on.regenerate_password_action,
|
||||||
self._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():
|
if self.bootstrapped():
|
||||||
self.bootstrap_status.set(ActiveStatus())
|
self.bootstrap_status.set(ActiveStatus())
|
||||||
|
|
||||||
@ -515,6 +542,108 @@ export OS_AUTH_VERSION=3
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
event.fail(f"Regeneration of password failed: {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):
|
def _on_peer_data_changed(self, event: RelationChangedEvent):
|
||||||
"""Process fernet updates if possible."""
|
"""Process fernet updates if possible."""
|
||||||
if self._state.unit_bootstrapped and self.is_leader_ready():
|
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
|
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__":
|
if __name__ == "__main__":
|
||||||
main(KeystoneOperatorCharm)
|
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
|
limit: 1
|
||||||
amqp:
|
amqp:
|
||||||
interface: rabbitmq
|
interface: rabbitmq
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -115,6 +115,12 @@ class MagnumConductorPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
|||||||
"magnum",
|
"magnum",
|
||||||
"magnum",
|
"magnum",
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
"magnum",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -232,6 +238,12 @@ class MagnumOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
"magnum",
|
"magnum",
|
||||||
"magnum",
|
"magnum",
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
"magnum",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
return _cconfigs
|
return _cconfigs
|
||||||
|
@ -13,7 +13,7 @@ db_auto_create = false
|
|||||||
{% include "parts/section-identity" %}
|
{% include "parts/section-identity" %}
|
||||||
|
|
||||||
[keystone_auth]
|
[keystone_auth]
|
||||||
{% include "parts/identity-data" %}
|
auth_section = keystone_authtoken
|
||||||
|
|
||||||
{% include "parts/section-service-user" %}
|
{% include "parts/section-service-user" %}
|
||||||
|
|
||||||
@ -32,3 +32,28 @@ region_name = RegionOne
|
|||||||
api_paste_config=/etc/magnum/api-paste.ini
|
api_paste_config=/etc/magnum/api-paste.ini
|
||||||
|
|
||||||
{% include "parts/section-oslo-messaging-rabbit" %}
|
{% 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:
|
certificates:
|
||||||
interface: tls-certificates
|
interface: tls-certificates
|
||||||
optional: true
|
optional: true
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -91,6 +91,12 @@ class NeutronServerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
|||||||
sunbeam_core.ContainerConfigFile(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/etc/neutron/api-paste.ini", "neutron", "neutron"
|
"/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(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/etc/neutron/api-paste.ini", "root", "neutron", 0o640
|
"/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
|
interface: neutron-api
|
||||||
placement:
|
placement:
|
||||||
interface: placement
|
interface: placement
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
cloud-controller:
|
cloud-controller:
|
||||||
|
@ -101,7 +101,13 @@ class NovaSchedulerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
|||||||
"root",
|
"root",
|
||||||
"nova",
|
"nova",
|
||||||
0o640,
|
0o640,
|
||||||
)
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
"nova",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -148,7 +154,13 @@ class NovaConductorPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
|||||||
"root",
|
"root",
|
||||||
"nova",
|
"nova",
|
||||||
0o640,
|
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",
|
"nova",
|
||||||
0o640,
|
0o640,
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
"nova",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
sunbeam_core.ContainerConfigFile(
|
sunbeam_core.ContainerConfigFile(
|
||||||
"/root/cell_create_wrapper.sh", "root", "root", 0o755
|
"/root/cell_create_wrapper.sh", "root", "root", 0o755
|
||||||
),
|
),
|
||||||
|
@ -76,6 +76,9 @@ requires:
|
|||||||
identity-ops:
|
identity-ops:
|
||||||
interface: keystone-resources
|
interface: keystone-resources
|
||||||
optional: true
|
optional: true
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
peers:
|
peers:
|
||||||
peers:
|
peers:
|
||||||
|
@ -221,6 +221,12 @@ class OctaviaOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
self.service_group,
|
self.service_group,
|
||||||
0o640,
|
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:
|
def handle_keystone_ops(self, event: ops.EventBase) -> None:
|
||||||
|
@ -28,6 +28,9 @@ resources:
|
|||||||
requires:
|
requires:
|
||||||
identity-ops:
|
identity-ops:
|
||||||
interface: keystone-resources
|
interface: keystone-resources
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
metrics-endpoint:
|
metrics-endpoint:
|
||||||
|
@ -161,6 +161,12 @@ class OSExporterOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
|
|||||||
"_daemon_",
|
"_daemon_",
|
||||||
"_daemon_",
|
"_daemon_",
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"_daemon_",
|
||||||
|
"_daemon_",
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -10,5 +10,8 @@ clouds:
|
|||||||
project_domain_name: {{ os_exporter.domain_name }}
|
project_domain_name: {{ os_exporter.domain_name }}
|
||||||
user_domain_name: {{ os_exporter.domain_name }}
|
user_domain_name: {{ os_exporter.domain_name }}
|
||||||
auth_url: {{ os_exporter.auth_url }}
|
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
|
verify: false
|
||||||
|
{% endif -%}
|
||||||
|
@ -40,6 +40,10 @@ requires:
|
|||||||
interface: ingress
|
interface: ingress
|
||||||
optional: true
|
optional: true
|
||||||
limit: 1
|
limit: 1
|
||||||
|
receive-ca-cert:
|
||||||
|
interface: certificate_transfer
|
||||||
|
optional: true
|
||||||
|
|
||||||
provides:
|
provides:
|
||||||
placement:
|
placement:
|
||||||
interface: placement
|
interface: placement
|
||||||
|
@ -95,6 +95,12 @@ class PlacementOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
|||||||
self.service_group,
|
self.service_group,
|
||||||
0o640,
|
0o640,
|
||||||
),
|
),
|
||||||
|
sunbeam_core.ContainerConfigFile(
|
||||||
|
"/usr/local/share/ca-certificates/ca-bundle.pem",
|
||||||
|
"root",
|
||||||
|
self.service_group,
|
||||||
|
0o640,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
return _cconfigs
|
return _cconfigs
|
||||||
|
|
||||||
|
195
common.sh
195
common.sh
@ -77,6 +77,7 @@ EXTERNAL_AODH_LIBS=(
|
|||||||
"data_platform_libs"
|
"data_platform_libs"
|
||||||
"rabbitmq_k8s"
|
"rabbitmq_k8s"
|
||||||
"traefik_k8s"
|
"traefik_k8s"
|
||||||
|
"certificate_transfer_interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_BARBICAN_LIBS=(
|
EXTERNAL_BARBICAN_LIBS=(
|
||||||
@ -84,10 +85,18 @@ EXTERNAL_BARBICAN_LIBS=(
|
|||||||
"rabbitmq_k8s"
|
"rabbitmq_k8s"
|
||||||
"traefik_k8s"
|
"traefik_k8s"
|
||||||
"vault_k8s"
|
"vault_k8s"
|
||||||
|
"certificate_transfer_interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_CEILOMETER_LIBS=(
|
EXTERNAL_CEILOMETER_LIBS=(
|
||||||
"rabbitmq_k8s"
|
"rabbitmq_k8s"
|
||||||
|
"certificate_transfer_interface"
|
||||||
|
)
|
||||||
|
|
||||||
|
EXTERNAL_CINDER_CEPH_LIBS=(
|
||||||
|
"data_platform_libs"
|
||||||
|
"rabbitmq_k8s"
|
||||||
|
"traefik_k8s"
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_DESIGNATE_BIND_LIBS=(
|
EXTERNAL_DESIGNATE_BIND_LIBS=(
|
||||||
@ -98,6 +107,7 @@ EXTERNAL_HEAT_LIBS=(
|
|||||||
"data_platform_libs"
|
"data_platform_libs"
|
||||||
"rabbitmq_k8s"
|
"rabbitmq_k8s"
|
||||||
"traefik_route_k8s"
|
"traefik_route_k8s"
|
||||||
|
"certificate_transfer_interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_NEUTRON_LIBS=(
|
EXTERNAL_NEUTRON_LIBS=(
|
||||||
@ -105,18 +115,21 @@ EXTERNAL_NEUTRON_LIBS=(
|
|||||||
"rabbitmq_k8s"
|
"rabbitmq_k8s"
|
||||||
"traefik_k8s"
|
"traefik_k8s"
|
||||||
"tls_certificates_interface"
|
"tls_certificates_interface"
|
||||||
|
"certificate_transfer_interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_OCTAVIA_LIBS=(
|
EXTERNAL_OCTAVIA_LIBS=(
|
||||||
"data_platform_libs"
|
"data_platform_libs"
|
||||||
"traefik_k8s"
|
"traefik_k8s"
|
||||||
"tls_certificates_interface"
|
"tls_certificates_interface"
|
||||||
|
"certificate_transfer_interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_OPENSTACK_EXPORTER_LIBS=(
|
EXTERNAL_OPENSTACK_EXPORTER_LIBS=(
|
||||||
"grafana_k8s"
|
"grafana_k8s"
|
||||||
"prometheus_k8s"
|
"prometheus_k8s"
|
||||||
"tls_certificates_interface"
|
"tls_certificates_interface"
|
||||||
|
"certificate_transfer_interface"
|
||||||
)
|
)
|
||||||
|
|
||||||
EXTERNAL_OPENSTACK_HYPERVISOR_LIBS=(
|
EXTERNAL_OPENSTACK_HYPERVISOR_LIBS=(
|
||||||
@ -150,118 +163,134 @@ EXTERNAL_TEMPEST_LIBS=(
|
|||||||
|
|
||||||
# Config template parts for each component.
|
# Config template parts for each component.
|
||||||
CONFIG_TEMPLATES_AODH=(
|
CONFIG_TEMPLATES_AODH=(
|
||||||
"section-database"
|
"parts/section-database"
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-service-credentials"
|
"parts/section-service-credentials"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_BARBICAN=(
|
CONFIG_TEMPLATES_BARBICAN=(
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-service-user"
|
"parts/section-service-user"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_CEILOMETER=(
|
CONFIG_TEMPLATES_CEILOMETER=(
|
||||||
"identity-data-id-creds"
|
"parts/identity-data-id-creds"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-service-credentials-from-identity-service"
|
"parts/section-service-credentials-from-identity-service"
|
||||||
"section-service-user-from-identity-credentials"
|
"parts/section-service-user-from-identity-credentials"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_CINDER=(
|
CONFIG_TEMPLATES_CINDER=(
|
||||||
"section-database"
|
"parts/section-database"
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-service-user"
|
"parts/section-service-user"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_CINDER_CEPH=(
|
CONFIG_TEMPLATES_CINDER_CEPH=(
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-oslo-notifications"
|
"parts/section-oslo-notifications"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_DESIGNATE=(
|
CONFIG_TEMPLATES_DESIGNATE=(
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-service-user"
|
"parts/section-service-user"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_GLANCE=(
|
CONFIG_TEMPLATES_GLANCE=(
|
||||||
"section-database"
|
"parts/section-database"
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-oslo-notifications"
|
"parts/section-oslo-notifications"
|
||||||
"section-service-user"
|
"parts/section-service-user"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_GNOCCHI=(
|
CONFIG_TEMPLATES_GNOCCHI=(
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_HEAT=(
|
CONFIG_TEMPLATES_HEAT=(
|
||||||
"section-database"
|
"parts/section-database"
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-trustee"
|
"parts/section-trustee"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
|
)
|
||||||
|
|
||||||
|
CONFIG_TEMPLATES_HORIZON=(
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_KEYSTONE=(
|
CONFIG_TEMPLATES_KEYSTONE=(
|
||||||
"section-database"
|
"parts/section-database"
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-federation"
|
"parts/section-federation"
|
||||||
"section-middleware"
|
"parts/section-middleware"
|
||||||
"section-oslo-cache"
|
"parts/section-oslo-cache"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-oslo-middleware"
|
"parts/section-oslo-middleware"
|
||||||
"section-oslo-notifications"
|
"parts/section-oslo-notifications"
|
||||||
"section-signing"
|
"parts/section-signing"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_MAGNUM=(
|
CONFIG_TEMPLATES_MAGNUM=(
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-service-user"
|
"parts/section-service-user"
|
||||||
"section-trust"
|
"parts/section-trust"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_NEUTRON=(
|
CONFIG_TEMPLATES_NEUTRON=(
|
||||||
"section-database"
|
"parts/section-database"
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-oslo-messaging-rabbit"
|
"parts/section-oslo-messaging-rabbit"
|
||||||
"section-service-user"
|
"parts/section-service-user"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_NOVA=${CONFIG_TEMPLATES_NEUTRON[@]}
|
CONFIG_TEMPLATES_NOVA=${CONFIG_TEMPLATES_NEUTRON[@]}
|
||||||
|
|
||||||
CONFIG_TEMPLATES_OCTAVIA=(
|
CONFIG_TEMPLATES_OCTAVIA=(
|
||||||
"section-database"
|
"parts/section-database"
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
CONFIG_TEMPLATES_PLACEMENT=(
|
CONFIG_TEMPLATES_PLACEMENT=(
|
||||||
"database-connection"
|
"parts/database-connection"
|
||||||
"section-identity"
|
"parts/section-identity"
|
||||||
"identity-data"
|
"parts/identity-data"
|
||||||
"section-service-user"
|
"parts/section-service-user"
|
||||||
|
"ca-bundle.pem.j2"
|
||||||
)
|
)
|
||||||
|
|
||||||
declare -A INTERNAL_LIBS=(
|
declare -A INTERNAL_LIBS=(
|
||||||
@ -297,7 +326,7 @@ declare -A EXTERNAL_LIBS=(
|
|||||||
[barbican-k8s]=${EXTERNAL_BARBICAN_LIBS[@]}
|
[barbican-k8s]=${EXTERNAL_BARBICAN_LIBS[@]}
|
||||||
[ceilometer-k8s]=${EXTERNAL_CEILOMETER_LIBS[@]}
|
[ceilometer-k8s]=${EXTERNAL_CEILOMETER_LIBS[@]}
|
||||||
[cinder-k8s]=${EXTERNAL_AODH_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-k8s]=${EXTERNAL_AODH_LIBS[@]}
|
||||||
[designate-bind-k8s]=${EXTERNAL_DESIGNATE_BIND_LIBS[@]}
|
[designate-bind-k8s]=${EXTERNAL_DESIGNATE_BIND_LIBS[@]}
|
||||||
[glance-k8s]=${EXTERNAL_AODH_LIBS[@]}
|
[glance-k8s]=${EXTERNAL_AODH_LIBS[@]}
|
||||||
@ -331,14 +360,14 @@ declare -A CONFIG_TEMPLATES=(
|
|||||||
[glance-k8s]=${CONFIG_TEMPLATES_GLANCE[@]}
|
[glance-k8s]=${CONFIG_TEMPLATES_GLANCE[@]}
|
||||||
[gnocchi-k8s]=${CONFIG_TEMPLATES_GNOCCHI[@]}
|
[gnocchi-k8s]=${CONFIG_TEMPLATES_GNOCCHI[@]}
|
||||||
[heat-k8s]=${CONFIG_TEMPLATES_HEAT[@]}
|
[heat-k8s]=${CONFIG_TEMPLATES_HEAT[@]}
|
||||||
[horizon-k8s]=${NULL_ARRAY[@]}
|
[horizon-k8s]=${CONFIG_TEMPLATES_HORIZON[@]}
|
||||||
[keystone-k8s]=${CONFIG_TEMPLATES_KEYSTONE[@]}
|
[keystone-k8s]=${CONFIG_TEMPLATES_KEYSTONE[@]}
|
||||||
[keystone-ldap-k8s]=${NULL_ARRAY[@]}
|
[keystone-ldap-k8s]=${NULL_ARRAY[@]}
|
||||||
[magnum-k8s]=${CONFIG_TEMPLATES_MAGNUM[@]}
|
[magnum-k8s]=${CONFIG_TEMPLATES_MAGNUM[@]}
|
||||||
[neutron-k8s]=${CONFIG_TEMPLATES_NEUTRON[@]}
|
[neutron-k8s]=${CONFIG_TEMPLATES_NEUTRON[@]}
|
||||||
[nova-k8s]=${CONFIG_TEMPLATES_NOVA[@]}
|
[nova-k8s]=${CONFIG_TEMPLATES_NOVA[@]}
|
||||||
[octavia-k8s]=${CONFIG_TEMPLATES_OCTAVIA[@]}
|
[octavia-k8s]=${CONFIG_TEMPLATES_OCTAVIA[@]}
|
||||||
[openstack-exporter-k8s]=${NULL_ARRAY[@]}
|
[openstack-exporter-k8s]=${CONFIG_TEMPLATES_HORIZON[@]}
|
||||||
[openstack-hypervisor]=${NULL_ARRAY[@]}
|
[openstack-hypervisor]=${NULL_ARRAY[@]}
|
||||||
[sunbeam-clusterd]=${NULL_ARRAY[@]}
|
[sunbeam-clusterd]=${NULL_ARRAY[@]}
|
||||||
[sunbeam-machine]=${NULL_ARRAY[@]}
|
[sunbeam-machine]=${NULL_ARRAY[@]}
|
||||||
@ -376,7 +405,7 @@ function copy_config_templates {
|
|||||||
config_templates_=${CONFIG_TEMPLATES[$1]}
|
config_templates_=${CONFIG_TEMPLATES[$1]}
|
||||||
for part in ${config_templates_[@]}; do
|
for part in ${config_templates_[@]}; do
|
||||||
echo "Copying $part"
|
echo "Copying $part"
|
||||||
cp -rf ../../templates/parts/$part src/templates/parts/
|
cp -rf ../../templates/$part src/templates/$part
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,6 +421,20 @@ function remove_libs {
|
|||||||
rm -rf lib
|
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 {
|
function remove_templates_parts_dir {
|
||||||
rm -rf src/templates/parts
|
rm -rf src/templates/parts
|
||||||
}
|
}
|
||||||
@ -430,7 +473,7 @@ function pop_common_files {
|
|||||||
pushd charms/$1
|
pushd charms/$1
|
||||||
|
|
||||||
remove_libs
|
remove_libs
|
||||||
remove_templates_parts_dir
|
remove_config_templates $1
|
||||||
remove_stestr_conf
|
remove_stestr_conf
|
||||||
remove_juju_ignore
|
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)
|
super().__init__(framework)
|
||||||
self.pebble_handlers = self.get_pebble_handlers()
|
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]:
|
def get_pebble_handlers(self) -> List[sunbeam_chandlers.PebbleHandler]:
|
||||||
"""Pebble handlers for the operator."""
|
"""Pebble handlers for the operator."""
|
||||||
return [
|
return [
|
||||||
|
@ -40,6 +40,7 @@ from ops.model import (
|
|||||||
ActiveStatus,
|
ActiveStatus,
|
||||||
BlockedStatus,
|
BlockedStatus,
|
||||||
SecretNotFoundError,
|
SecretNotFoundError,
|
||||||
|
Unit,
|
||||||
UnknownStatus,
|
UnknownStatus,
|
||||||
WaitingStatus,
|
WaitingStatus,
|
||||||
)
|
)
|
||||||
@ -1781,3 +1782,84 @@ class UserIdentityResourceRequiresHandler(RelationHandler):
|
|||||||
def ready(self) -> bool:
|
def ready(self) -> bool:
|
||||||
"""Whether the relation is ready."""
|
"""Whether the relation is ready."""
|
||||||
return self.get_config_credentials() is not None
|
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 -%}
|
{% if identity_service.internal_auth_url -%}
|
||||||
auth_url = {{ identity_service.admin_auth_url }}
|
|
||||||
interface = admin
|
|
||||||
{% elif identity_service.internal_auth_url -%}
|
|
||||||
auth_url = {{ identity_service.internal_auth_url }}
|
auth_url = {{ identity_service.internal_auth_url }}
|
||||||
interface = internal
|
interface = internal
|
||||||
|
{% elif identity_service.admin_auth_url -%}
|
||||||
|
auth_url = {{ identity_service.admin_auth_url }}
|
||||||
|
interface = admin
|
||||||
{% elif identity_service.internal_host -%}
|
{% elif identity_service.internal_host -%}
|
||||||
auth_url = {{ identity_service.internal_protocol }}://{{ identity_service.internal_host }}:{{ identity_service.internal_port }}
|
auth_url = {{ identity_service.internal_protocol }}://{{ identity_service.internal_host }}:{{ identity_service.internal_port }}
|
||||||
interface = internal
|
interface = internal
|
||||||
@ -19,5 +19,8 @@ user_domain_name = {{ identity_service.service_domain_name }}
|
|||||||
project_name = {{ identity_service.service_project_name }}
|
project_name = {{ identity_service.service_project_name }}
|
||||||
username = {{ identity_service.service_user_name }}
|
username = {{ identity_service.service_user_name }}
|
||||||
password = {{ identity_service.service_password }}
|
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 = {{ identity_service.admin_role }}
|
||||||
service_token_roles_required = True
|
service_token_roles_required = True
|
||||||
|
@ -19,5 +19,8 @@ user_domain_name = {{ identity_credentials.user_domain_name }}
|
|||||||
project_name = {{ identity_credentials.project_name }}
|
project_name = {{ identity_credentials.project_name }}
|
||||||
username = {{ identity_credentials.username }}
|
username = {{ identity_credentials.username }}
|
||||||
password = {{ identity_credentials.password }}
|
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 = {{ identity_credentials.admin_role }}
|
||||||
service_token_roles_required = True
|
service_token_roles_required = True
|
||||||
|
@ -141,6 +141,8 @@ relations:
|
|||||||
- glance:amqp
|
- glance:amqp
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- glance:ingress-public
|
- glance:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- glance:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- heat:database
|
- heat:database
|
||||||
@ -152,6 +154,8 @@ relations:
|
|||||||
- heat:traefik-route-public
|
- heat:traefik-route-public
|
||||||
- - rabbitmq:amqp
|
- - rabbitmq:amqp
|
||||||
- heat:amqp
|
- heat:amqp
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- heat:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- octavia:database
|
- octavia:database
|
||||||
@ -165,6 +169,8 @@ relations:
|
|||||||
- octavia:certificates
|
- octavia:certificates
|
||||||
- - octavia:ovsdb-cms
|
- - octavia:ovsdb-cms
|
||||||
- ovn-central:ovsdb-cms
|
- ovn-central:ovsdb-cms
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- octavia:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- barbican:database
|
- barbican:database
|
||||||
@ -178,6 +184,8 @@ relations:
|
|||||||
- barbican:ingress-public
|
- barbican:ingress-public
|
||||||
- - vault:vault-kv
|
- - vault:vault-kv
|
||||||
- barbican:vault-kv
|
- barbican:vault-kv
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- barbican:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- magnum:database
|
- magnum:database
|
||||||
@ -189,3 +197,5 @@ relations:
|
|||||||
- magnum:identity-ops
|
- magnum:identity-ops
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- magnum:ingress-public
|
- magnum:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- magnum:receive-ca-cert
|
||||||
|
@ -114,6 +114,8 @@ relations:
|
|||||||
- cinder:identity-service
|
- cinder:identity-service
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- cinder:ingress-public
|
- cinder:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- cinder:receive-ca-cert
|
||||||
|
|
||||||
- - cinder-ceph:database
|
- - cinder-ceph:database
|
||||||
- mysql:database
|
- mysql:database
|
||||||
@ -128,6 +130,8 @@ relations:
|
|||||||
- gnocchi:ingress-public
|
- gnocchi:ingress-public
|
||||||
- - keystone:identity-service
|
- - keystone:identity-service
|
||||||
- gnocchi:identity-service
|
- gnocchi:identity-service
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- gnocchi:receive-ca-cert
|
||||||
|
|
||||||
- - rabbitmq:amqp
|
- - rabbitmq:amqp
|
||||||
- ceilometer:amqp
|
- ceilometer:amqp
|
||||||
@ -135,6 +139,8 @@ relations:
|
|||||||
- ceilometer:identity-credentials
|
- ceilometer:identity-credentials
|
||||||
- - gnocchi:gnocchi-service
|
- - gnocchi:gnocchi-service
|
||||||
- ceilometer:gnocchi-db
|
- ceilometer:gnocchi-db
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- ceilometer:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- aodh:database
|
- aodh:database
|
||||||
@ -144,3 +150,5 @@ relations:
|
|||||||
- aodh:identity-service
|
- aodh:identity-service
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- aodh:ingress-public
|
- aodh:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- aodh:receive-ca-cert
|
||||||
|
@ -151,6 +151,8 @@ relations:
|
|||||||
- glance:amqp
|
- glance:amqp
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- glance:ingress-public
|
- glance:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- glance:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- nova:database
|
- nova:database
|
||||||
@ -164,6 +166,8 @@ relations:
|
|||||||
- nova:identity-service
|
- nova:identity-service
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- nova:ingress-public
|
- nova:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- nova:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- placement:database
|
- placement:database
|
||||||
@ -171,6 +175,8 @@ relations:
|
|||||||
- placement:identity-service
|
- placement:identity-service
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- placement:ingress-public
|
- placement:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- placement:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- neutron:database
|
- neutron:database
|
||||||
@ -184,6 +190,8 @@ relations:
|
|||||||
- neutron:certificates
|
- neutron:certificates
|
||||||
- - neutron:ovsdb-cms
|
- - neutron:ovsdb-cms
|
||||||
- ovn-central:ovsdb-cms
|
- ovn-central:ovsdb-cms
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- neutron:receive-ca-cert
|
||||||
|
|
||||||
- - mysql:database
|
- - mysql:database
|
||||||
- horizon:database
|
- horizon:database
|
||||||
@ -191,3 +199,5 @@ relations:
|
|||||||
- horizon:identity-credentials
|
- horizon:identity-credentials
|
||||||
- - traefik:ingress
|
- - traefik:ingress
|
||||||
- horizon:ingress-public
|
- horizon:ingress-public
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- horizon:receive-ca-cert
|
||||||
|
@ -98,6 +98,8 @@ relations:
|
|||||||
- designate:ingress-public
|
- designate:ingress-public
|
||||||
- - designate-bind:dns-backend
|
- - designate-bind:dns-backend
|
||||||
- designate:dns-backend
|
- designate:dns-backend
|
||||||
|
- - keystone:send-ca-cert
|
||||||
|
- designate:receive-ca-cert
|
||||||
|
|
||||||
- - keystone:domain-config
|
- - keystone:domain-config
|
||||||
- keystone-ldap:domain-config
|
- keystone-ldap:domain-config
|
||||||
|
Loading…
x
Reference in New Issue
Block a user