From 897fb18137c862c0d31c6101de8d5e29cfc510ab Mon Sep 17 00:00:00 2001 From: Onur Musaoglu Date: Thu, 12 Sep 2024 11:12:44 +0300 Subject: [PATCH 1/3] updated vault lib --- lib/charms/vault_k8s/v0/vault_kv.py | 108 ++++++++++++++++++++-------- 1 file changed, 79 insertions(+), 29 deletions(-) diff --git a/lib/charms/vault_k8s/v0/vault_kv.py b/lib/charms/vault_k8s/v0/vault_kv.py index 7bfab13..37da63e 100644 --- a/lib/charms/vault_k8s/v0/vault_kv.py +++ b/lib/charms/vault_k8s/v0/vault_kv.py @@ -54,8 +54,9 @@ def _on_install(self, event: InstallEvent): def _on_connected(self, event: vault_kv.VaultKvConnectedEvent): relation = self.model.get_relation(event.relation_name, event.relation_id) - egress_subnet = str(self.model.get_binding(relation).network.interfaces[0].subnet) - self.interface.request_credentials(relation, egress_subnet, self.get_nonce()) + egress_subnets = [str(subnet) for subnet in self.model.get_binding(relation).network.egress_subnets][0].subnet] + egress_subnets.append(str(self.model.get_binding(relation).network.interfaces[0].subnet)) + self.interface.request_credentials(relation, egress_subnets, self.get_nonce()) def _on_ready(self, event: vault_kv.VaultKvReadyEvent): relation = self.model.get_relation(event.relation_name, event.relation_id) @@ -94,9 +95,10 @@ def _on_update_status(self, event): # Update status might not be the best place binding = self.model.get_binding("vault-kv") if binding is not None: - egress_subnet = str(binding.network.interfaces[0].subnet) + egress_subnets = [str(subnet) for subnet in self.model.get_binding(relation).network.egress_subnets][0].subnet] + egress_subnets.append(str(self.model.get_binding(relation).network.interfaces[0].subnet)) relation = self.model.get_relation(relation_name="vault-kv") - self.interface.request_credentials(relation, egress_subnet, self.get_nonce()) + self.interface.request_credentials(relation, egress_subnets, self.get_nonce()) def get_nonce(self): secret = self.model.get_secret(label=NONCE_SECRET_LABEL) @@ -133,7 +135,7 @@ def get_nonce(self): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 9 +LIBPATCH = 11 PYDEPS = ["pydantic", "pytest-interface-tester"] @@ -164,7 +166,7 @@ class VaultKvProviderSchema(BaseModel): ca_certificate: str = Field( description="The CA certificate to use when validating the Vault server's certificate." ) - egress_subnet: str = Field(description="The CIDR allowed by the role.") + egress_subnet: str = Field(description="The CIDRs allowed by the role separated by commas.") credentials: Json[Mapping[str, str]] = Field( description=( "Mapping of unit name and credentials for that unit." @@ -184,7 +186,9 @@ class AppVaultKvRequirerSchema(BaseModel): class UnitVaultKvRequirerSchema(BaseModel): """Unit schema of the requirer side of the vault-kv interface.""" - egress_subnet: str = Field(description="Egress subnet to use, in CIDR notation.") + egress_subnet: str = Field( + description="Egress subnets to use separated by commas, in CIDR notation." + ) nonce: str = Field( description="Uniquely identifying value for this unit. `secrets.token_hex(16)` is recommended." ) @@ -211,10 +215,21 @@ class KVRequest: app_name: str unit_name: str mount_suffix: str - egress_subnet: str + egress_subnets: List[str] nonce: str +def get_egress_subnets_list_from_relation_data(relation_databag: Mapping[str, str]) -> List[str]: + """Return the egress_subnet as a list. + + This function converts the string with values separated by commas to a list. + + Args: + relation_databag: the relation databag of the unit or the app. + """ + return [subnet.strip() for subnet in relation_databag.get("egress_subnet", "").split(",")] + + def is_requirer_data_valid(app_data: Mapping[str, str], unit_data: Mapping[str, str]) -> bool: """Return whether the requirer data is valid.""" try: @@ -238,6 +253,31 @@ def is_provider_data_valid(data: Mapping[str, str]) -> bool: return False +class VaultKvGoneAwayEvent(ops.EventBase): + """VaultKvGoneAwayEvent Event.""" + + pass + + +class VaultKvClientDetachedEvent(ops.EventBase): + """VaultKvClientDetachedEvent Event.""" + + def __init__(self, handle: ops.Handle, unit_name: str): + super().__init__(handle) + self.unit_name = unit_name + + def snapshot(self) -> Dict[str, Any]: + """Return snapshot data that should be persisted.""" + return { + "unit_name": self.unit_name, + } + + def restore(self, snapshot: Dict[str, Any]) -> None: + """Restore the event from a snapshot.""" + super().restore(snapshot) + self.unit_name = snapshot["unit_name"] + + class NewVaultKvClientAttachedEvent(ops.EventBase): """New vault kv client attached event.""" @@ -248,7 +288,7 @@ def __init__( app_name: str, unit_name: str, mount_suffix: str, - egress_subnet: str, + egress_subnets: List[str], nonce: str, ): super().__init__(handle) @@ -256,7 +296,7 @@ def __init__( self.app_name = app_name self.unit_name = unit_name self.mount_suffix = mount_suffix - self.egress_subnet = egress_subnet + self.egress_subnets = egress_subnets self.nonce = nonce def snapshot(self) -> dict: @@ -266,7 +306,7 @@ def snapshot(self) -> dict: "app_name": self.app_name, "unit_name": self.unit_name, "mount_suffix": self.mount_suffix, - "egress_subnet": self.egress_subnet, + "egress_subnets": self.egress_subnets, "nonce": self.nonce, } @@ -277,7 +317,7 @@ def restore(self, snapshot: Dict[str, Any]): self.app_name = snapshot["app_name"] self.unit_name = snapshot["unit_name"] self.mount_suffix = snapshot["mount_suffix"] - self.egress_subnet = snapshot["egress_subnet"] + self.egress_subnets = snapshot["egress_subnets"] self.nonce = snapshot["nonce"] @@ -285,6 +325,7 @@ class VaultKvProviderEvents(ops.ObjectEvents): """List of events that the Vault Kv provider charm can leverage.""" new_vault_kv_client_attached = ops.EventSource(NewVaultKvClientAttachedEvent) + vault_kv_client_detached = ops.EventSource(VaultKvClientDetachedEvent) class VaultKvProvides(ops.Object): @@ -304,6 +345,10 @@ def __init__( self.charm.on[relation_name].relation_changed, self._on_relation_changed, ) + self.framework.observe( + self.charm.on[relation_name].relation_departed, + self._on_vault_kv_relation_departed, + ) def _on_relation_changed(self, event: ops.RelationChangedEvent): """Handle client changed relation. @@ -324,10 +369,17 @@ def _on_relation_changed(self, event: ops.RelationChangedEvent): app_name=event.app.name, unit_name=unit.name, mount_suffix=event.relation.data[event.app]["mount_suffix"], - egress_subnet=event.relation.data[unit]["egress_subnet"], + egress_subnets=get_egress_subnets_list_from_relation_data( + event.relation.data[unit] + ), nonce=event.relation.data[unit]["nonce"], ) + def _on_vault_kv_relation_departed(self, event: ops.RelationDepartedEvent): + """Handle relation departed.""" + if event.departing_unit: + self.on.vault_kv_client_detached.emit(unit_name=event.departing_unit.name) + def set_vault_url(self, relation: ops.Relation, vault_url: str): """Set the vault_url on the relation.""" if not self.charm.unit.is_leader(): @@ -354,11 +406,11 @@ def set_mount(self, relation: ops.Relation, mount: str): relation.data[self.charm.app]["mount"] = mount - def set_egress_subnet(self, relation: ops.Relation, egress_subnet: str): - """Set the egress_subnet on the relation.""" + def set_egress_subnets(self, relation: ops.Relation, egress_subnets: List[str]): + """Set the egress_subnets on the relation.""" if not self.charm.unit.is_leader(): return - relation.data[self.charm.app]["egress_subnet"] = egress_subnet + relation.data[self.charm.app]["egress_subnet"] = ",".join(egress_subnets) def set_unit_credentials( self, @@ -439,7 +491,7 @@ def get_kv_requests(self, relation_id: Optional[int] = None) -> List[KVRequest]: app_name=relation.app.name, unit_name=unit.name, mount_suffix=app_data["mount_suffix"], - egress_subnet=unit_data["egress_subnet"], + egress_subnets=get_egress_subnets_list_from_relation_data(unit_data), nonce=unit_data["nonce"], ) ) @@ -508,12 +560,6 @@ def restore(self, snapshot: Dict[str, Any]): self.relation_name = snapshot["relation_name"] -class VaultKvGoneAwayEvent(ops.EventBase): - """VaultKvGoneAwayEvent Event.""" - - pass - - class VaultKvRequireEvents(ops.ObjectEvents): """List of events that the Vault Kv requirer charm can leverage.""" @@ -558,9 +604,9 @@ def _set_unit_nonce(self, relation: ops.Relation, nonce: str): """Set the nonce on the relation.""" relation.data[self.charm.unit]["nonce"] = nonce - def _set_unit_egress_subnet(self, relation: ops.Relation, egress_subnet: str): - """Set the egress_subnet on the relation.""" - relation.data[self.charm.unit]["egress_subnet"] = egress_subnet + def _set_unit_egress_subnets(self, relation: ops.Relation, egress_subnets: List[str]): + """Set the egress_subnets on the relation.""" + relation.data[self.charm.unit]["egress_subnet"] = ",".join(egress_subnets) def _handle_relation(self, event: ops.EventBase): """Run when a new unit joins the relation or when the address of the unit changes. @@ -597,16 +643,20 @@ def _on_vault_kv_relation_broken(self, event: ops.RelationBrokenEvent): """Handle relation broken.""" self.on.gone_away.emit() - def request_credentials(self, relation: ops.Relation, egress_subnet: str, nonce: str) -> None: + def request_credentials( + self, relation: ops.Relation, egress_subnet: Union[List[str], str], nonce: str + ) -> None: """Request credentials from the vault-kv relation. Generated secret ids are tied to the unit egress_subnet, so if the egress_subnet changes a new secret id must be generated. - A change in egress_subnet can happen when the pod is rescheduled to a different + A change in egress_subnets can happen when the pod is rescheduled to a different node by the underlying substrate without a change from Juju. """ - self._set_unit_egress_subnet(relation, egress_subnet) + if isinstance(egress_subnet, str): + egress_subnet = [egress_subnet] + self._set_unit_egress_subnets(relation, egress_subnet) self._set_unit_nonce(relation, nonce) def get_vault_url(self, relation: ops.Relation) -> Optional[str]: From 8039a4183ddb7378ec7249c9a11259c87b249cf0 Mon Sep 17 00:00:00 2001 From: Onur Musaoglu Date: Sat, 14 Sep 2024 12:59:38 +0300 Subject: [PATCH 2/3] Fixed bug for overriding rock environment variables --- src/charm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/charm.py b/src/charm.py index 9533710..61e368f 100755 --- a/src/charm.py +++ b/src/charm.py @@ -254,6 +254,9 @@ def _update(self, event): # noqa: C901 return context = {} + context.update( + {convert_env_var(key): value for key, value in self.config.items() if key not in ["environment"]} + ) try: self._validate(event) environment_config = self.config.get("environment") @@ -277,9 +280,6 @@ def _update(self, event): # noqa: C901 if value: context.update({key: value}) - context.update( - {convert_env_var(key): value for key, value in self.config.items() if key not in ["environment"]} - ) context.update({"TWC_PROMETHEUS_PORT": PROMETHEUS_PORT}) pebble_layer = { From fc47222de65706037fb191cc78fad17ec9790945 Mon Sep 17 00:00:00 2001 From: Onur Musaoglu Date: Sun, 15 Sep 2024 12:33:54 +0300 Subject: [PATCH 3/3] Revert "updated vault lib" This reverts commit 897fb18137c862c0d31c6101de8d5e29cfc510ab. --- lib/charms/vault_k8s/v0/vault_kv.py | 108 ++++++++-------------------- 1 file changed, 29 insertions(+), 79 deletions(-) diff --git a/lib/charms/vault_k8s/v0/vault_kv.py b/lib/charms/vault_k8s/v0/vault_kv.py index 37da63e..7bfab13 100644 --- a/lib/charms/vault_k8s/v0/vault_kv.py +++ b/lib/charms/vault_k8s/v0/vault_kv.py @@ -54,9 +54,8 @@ def _on_install(self, event: InstallEvent): def _on_connected(self, event: vault_kv.VaultKvConnectedEvent): relation = self.model.get_relation(event.relation_name, event.relation_id) - egress_subnets = [str(subnet) for subnet in self.model.get_binding(relation).network.egress_subnets][0].subnet] - egress_subnets.append(str(self.model.get_binding(relation).network.interfaces[0].subnet)) - self.interface.request_credentials(relation, egress_subnets, self.get_nonce()) + egress_subnet = str(self.model.get_binding(relation).network.interfaces[0].subnet) + self.interface.request_credentials(relation, egress_subnet, self.get_nonce()) def _on_ready(self, event: vault_kv.VaultKvReadyEvent): relation = self.model.get_relation(event.relation_name, event.relation_id) @@ -95,10 +94,9 @@ def _on_update_status(self, event): # Update status might not be the best place binding = self.model.get_binding("vault-kv") if binding is not None: - egress_subnets = [str(subnet) for subnet in self.model.get_binding(relation).network.egress_subnets][0].subnet] - egress_subnets.append(str(self.model.get_binding(relation).network.interfaces[0].subnet)) + egress_subnet = str(binding.network.interfaces[0].subnet) relation = self.model.get_relation(relation_name="vault-kv") - self.interface.request_credentials(relation, egress_subnets, self.get_nonce()) + self.interface.request_credentials(relation, egress_subnet, self.get_nonce()) def get_nonce(self): secret = self.model.get_secret(label=NONCE_SECRET_LABEL) @@ -135,7 +133,7 @@ def get_nonce(self): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 11 +LIBPATCH = 9 PYDEPS = ["pydantic", "pytest-interface-tester"] @@ -166,7 +164,7 @@ class VaultKvProviderSchema(BaseModel): ca_certificate: str = Field( description="The CA certificate to use when validating the Vault server's certificate." ) - egress_subnet: str = Field(description="The CIDRs allowed by the role separated by commas.") + egress_subnet: str = Field(description="The CIDR allowed by the role.") credentials: Json[Mapping[str, str]] = Field( description=( "Mapping of unit name and credentials for that unit." @@ -186,9 +184,7 @@ class AppVaultKvRequirerSchema(BaseModel): class UnitVaultKvRequirerSchema(BaseModel): """Unit schema of the requirer side of the vault-kv interface.""" - egress_subnet: str = Field( - description="Egress subnets to use separated by commas, in CIDR notation." - ) + egress_subnet: str = Field(description="Egress subnet to use, in CIDR notation.") nonce: str = Field( description="Uniquely identifying value for this unit. `secrets.token_hex(16)` is recommended." ) @@ -215,21 +211,10 @@ class KVRequest: app_name: str unit_name: str mount_suffix: str - egress_subnets: List[str] + egress_subnet: str nonce: str -def get_egress_subnets_list_from_relation_data(relation_databag: Mapping[str, str]) -> List[str]: - """Return the egress_subnet as a list. - - This function converts the string with values separated by commas to a list. - - Args: - relation_databag: the relation databag of the unit or the app. - """ - return [subnet.strip() for subnet in relation_databag.get("egress_subnet", "").split(",")] - - def is_requirer_data_valid(app_data: Mapping[str, str], unit_data: Mapping[str, str]) -> bool: """Return whether the requirer data is valid.""" try: @@ -253,31 +238,6 @@ def is_provider_data_valid(data: Mapping[str, str]) -> bool: return False -class VaultKvGoneAwayEvent(ops.EventBase): - """VaultKvGoneAwayEvent Event.""" - - pass - - -class VaultKvClientDetachedEvent(ops.EventBase): - """VaultKvClientDetachedEvent Event.""" - - def __init__(self, handle: ops.Handle, unit_name: str): - super().__init__(handle) - self.unit_name = unit_name - - def snapshot(self) -> Dict[str, Any]: - """Return snapshot data that should be persisted.""" - return { - "unit_name": self.unit_name, - } - - def restore(self, snapshot: Dict[str, Any]) -> None: - """Restore the event from a snapshot.""" - super().restore(snapshot) - self.unit_name = snapshot["unit_name"] - - class NewVaultKvClientAttachedEvent(ops.EventBase): """New vault kv client attached event.""" @@ -288,7 +248,7 @@ def __init__( app_name: str, unit_name: str, mount_suffix: str, - egress_subnets: List[str], + egress_subnet: str, nonce: str, ): super().__init__(handle) @@ -296,7 +256,7 @@ def __init__( self.app_name = app_name self.unit_name = unit_name self.mount_suffix = mount_suffix - self.egress_subnets = egress_subnets + self.egress_subnet = egress_subnet self.nonce = nonce def snapshot(self) -> dict: @@ -306,7 +266,7 @@ def snapshot(self) -> dict: "app_name": self.app_name, "unit_name": self.unit_name, "mount_suffix": self.mount_suffix, - "egress_subnets": self.egress_subnets, + "egress_subnet": self.egress_subnet, "nonce": self.nonce, } @@ -317,7 +277,7 @@ def restore(self, snapshot: Dict[str, Any]): self.app_name = snapshot["app_name"] self.unit_name = snapshot["unit_name"] self.mount_suffix = snapshot["mount_suffix"] - self.egress_subnets = snapshot["egress_subnets"] + self.egress_subnet = snapshot["egress_subnet"] self.nonce = snapshot["nonce"] @@ -325,7 +285,6 @@ class VaultKvProviderEvents(ops.ObjectEvents): """List of events that the Vault Kv provider charm can leverage.""" new_vault_kv_client_attached = ops.EventSource(NewVaultKvClientAttachedEvent) - vault_kv_client_detached = ops.EventSource(VaultKvClientDetachedEvent) class VaultKvProvides(ops.Object): @@ -345,10 +304,6 @@ def __init__( self.charm.on[relation_name].relation_changed, self._on_relation_changed, ) - self.framework.observe( - self.charm.on[relation_name].relation_departed, - self._on_vault_kv_relation_departed, - ) def _on_relation_changed(self, event: ops.RelationChangedEvent): """Handle client changed relation. @@ -369,17 +324,10 @@ def _on_relation_changed(self, event: ops.RelationChangedEvent): app_name=event.app.name, unit_name=unit.name, mount_suffix=event.relation.data[event.app]["mount_suffix"], - egress_subnets=get_egress_subnets_list_from_relation_data( - event.relation.data[unit] - ), + egress_subnet=event.relation.data[unit]["egress_subnet"], nonce=event.relation.data[unit]["nonce"], ) - def _on_vault_kv_relation_departed(self, event: ops.RelationDepartedEvent): - """Handle relation departed.""" - if event.departing_unit: - self.on.vault_kv_client_detached.emit(unit_name=event.departing_unit.name) - def set_vault_url(self, relation: ops.Relation, vault_url: str): """Set the vault_url on the relation.""" if not self.charm.unit.is_leader(): @@ -406,11 +354,11 @@ def set_mount(self, relation: ops.Relation, mount: str): relation.data[self.charm.app]["mount"] = mount - def set_egress_subnets(self, relation: ops.Relation, egress_subnets: List[str]): - """Set the egress_subnets on the relation.""" + def set_egress_subnet(self, relation: ops.Relation, egress_subnet: str): + """Set the egress_subnet on the relation.""" if not self.charm.unit.is_leader(): return - relation.data[self.charm.app]["egress_subnet"] = ",".join(egress_subnets) + relation.data[self.charm.app]["egress_subnet"] = egress_subnet def set_unit_credentials( self, @@ -491,7 +439,7 @@ def get_kv_requests(self, relation_id: Optional[int] = None) -> List[KVRequest]: app_name=relation.app.name, unit_name=unit.name, mount_suffix=app_data["mount_suffix"], - egress_subnets=get_egress_subnets_list_from_relation_data(unit_data), + egress_subnet=unit_data["egress_subnet"], nonce=unit_data["nonce"], ) ) @@ -560,6 +508,12 @@ def restore(self, snapshot: Dict[str, Any]): self.relation_name = snapshot["relation_name"] +class VaultKvGoneAwayEvent(ops.EventBase): + """VaultKvGoneAwayEvent Event.""" + + pass + + class VaultKvRequireEvents(ops.ObjectEvents): """List of events that the Vault Kv requirer charm can leverage.""" @@ -604,9 +558,9 @@ def _set_unit_nonce(self, relation: ops.Relation, nonce: str): """Set the nonce on the relation.""" relation.data[self.charm.unit]["nonce"] = nonce - def _set_unit_egress_subnets(self, relation: ops.Relation, egress_subnets: List[str]): - """Set the egress_subnets on the relation.""" - relation.data[self.charm.unit]["egress_subnet"] = ",".join(egress_subnets) + def _set_unit_egress_subnet(self, relation: ops.Relation, egress_subnet: str): + """Set the egress_subnet on the relation.""" + relation.data[self.charm.unit]["egress_subnet"] = egress_subnet def _handle_relation(self, event: ops.EventBase): """Run when a new unit joins the relation or when the address of the unit changes. @@ -643,20 +597,16 @@ def _on_vault_kv_relation_broken(self, event: ops.RelationBrokenEvent): """Handle relation broken.""" self.on.gone_away.emit() - def request_credentials( - self, relation: ops.Relation, egress_subnet: Union[List[str], str], nonce: str - ) -> None: + def request_credentials(self, relation: ops.Relation, egress_subnet: str, nonce: str) -> None: """Request credentials from the vault-kv relation. Generated secret ids are tied to the unit egress_subnet, so if the egress_subnet changes a new secret id must be generated. - A change in egress_subnets can happen when the pod is rescheduled to a different + A change in egress_subnet can happen when the pod is rescheduled to a different node by the underlying substrate without a change from Juju. """ - if isinstance(egress_subnet, str): - egress_subnet = [egress_subnet] - self._set_unit_egress_subnets(relation, egress_subnet) + self._set_unit_egress_subnet(relation, egress_subnet) self._set_unit_nonce(relation, nonce) def get_vault_url(self, relation: ops.Relation) -> Optional[str]: