canonical_service_mesh.interfaces.tailscale_credentials¶
Tailscale credentials interface library.
This library provides the provider and requirer sides of the
tailscale-credentials relation interface. The provider (tailscale-config)
mints a per-relation credential against the control-plane API and
distributes it to one downstream charm (tailscale-k8s / tailscale-beacon);
it revokes the credential when the relation is removed.
What is this library for?¶
The credential is sensitive, so it travels as a Juju charm secret rather than as
plaintext in the databag. The provider adds and grants the secret, then publishes
its URI (plus the non-secret login-server and tags) on its app databag.
The requirer reads the URI and fetches the secret content on demand. This works
over cross-model relations: only the secret URI crosses the databag, while the
content is fetched via Juju’s controller channel.
This library is deliberately thin. It contains only pydantic models and databag /
secret-content helpers and makes no live ops calls. The charm owns
the secret add_secret / grant / get_secret calls.
Provider usage (tailscale-config charm):
from canonical_service_mesh.interfaces.tailscale_credentials import (
ProviderAppData,
TailscaleCredentials,
TailscaleCredentialsProvider,
)
class MyConfigCharm(CharmBase):
def __init__(self, framework):
super().__init__(framework)
self.creds = TailscaleCredentialsProvider(self.model.relations, self.app)
def _reconcile(self, relation):
# Charm mints the child credential against the control plane, then:
content = TailscaleCredentials(
auth_key="tskey-client-...", client_id="key-id-...",
).to_secret_content()
secret = self.app.add_secret(content)
secret.grant(relation)
self.creds.publish(
relation,
ProviderAppData(
secret_id=secret.id,
login_server="https://controlplane.example.com",
tags=["tag:child"],
),
)
Requirer usage (tailscale-k8s / tailscale-beacon charm):
from canonical_service_mesh.interfaces.tailscale_credentials import (
TailscaleCredentials,
TailscaleCredentialsRequirer,
)
class MyBackendCharm(CharmBase):
def __init__(self, framework):
super().__init__(framework)
self.creds = TailscaleCredentialsRequirer(self.model.relations, self.app)
def _on_relation_changed(self, event):
# A downstream charm has at most one tailscale-credentials relation.
relation = self.model.get_relation("tailscale-credentials")
if relation is None or not self.creds.is_ready(relation):
return
provider_data = self.creds.get_provider_data(relation)
secret = self.model.get_secret(id=provider_data.secret_id)
credentials = TailscaleCredentials.model_validate(secret.get_content())
# tailscale up --auth-key=credentials.auth_key, etc.
...
Attributes¶
Classes¶
Non-secret provider app databag for the |
|
Credential secret content for the Tailscale backend. |
|
Provider side wrapper for the |
|
Requirer side wrapper for the |
Package Contents¶
- class canonical_service_mesh.interfaces.tailscale_credentials.ProviderAppData¶
Bases:
pydantic.BaseModelNon-secret provider app databag for the
tailscale-credentialsrelation.Published by the provider to the requirer. The sensitive credential itself travels as a Juju charm secret; only its URI (
secret_id) is carried here.- is_ready_for_use() bool¶
Check whether this data represents a usable credential.
- Returns:
True if both
secret_idandlogin_serverare set.
- to_databag() dict[str, str]¶
Serialize to a flat
dict[str, str]for the relation databag.- Returns:
A flat
dict[str, str]with wire-encoded values, ready to write to the provider app databag.
- login_server: str | None¶
- secret_id: str | None¶
- tags: list[str] | None¶
- class canonical_service_mesh.interfaces.tailscale_credentials.TailscaleCredentials¶
Bases:
pydantic.BaseModelCredential secret content for the Tailscale backend.
- to_secret_content() dict[str, str]¶
Serialize to a flat
dict[str, str]for a Juju secret’s content.- Returns:
A flat
dict[str, str]suitable for the charm’sadd_secret.
- auth_key: str¶
- client_id: str¶
- model_config¶
- class canonical_service_mesh.interfaces.tailscale_credentials.TailscaleCredentialsProvider(relation_mapping: ops.RelationMapping, app: ops.Application, relation_name: str = DEFAULT_RELATION_NAME)¶
Provider side wrapper for the
tailscale-credentialsrelation.The provider publishes the non-secret app databag (secret URI, login-server, tags). The charm owns the secret lifecycle, the peer map, and control-plane minting; it builds the secret content via
TailscaleCredentials.to_secret_content().Initialize the TailscaleCredentialsProvider.
- Parameters:
relation_mapping – The charm’s RelationMapping (typically self.model.relations).
app – This application (the tailscale-config charm).
relation_name – The name of the relation.
- publish(relation: ops.Relation, data: ProviderAppData) None¶
Publish the provider app data to a specific relation.
- Parameters:
relation – A specific relation instance.
data – The provider app data to publish.
login_servermust be non-empty.
- property relations: list[ops.Relation]¶
Return the relation instances for the monitored relation.
- class canonical_service_mesh.interfaces.tailscale_credentials.TailscaleCredentialsRequirer(relation_mapping: ops.RelationMapping, app: ops.Application, relation_name: str = DEFAULT_RELATION_NAME)¶
Requirer side wrapper for the
tailscale-credentialsrelation.The requirer reads the non-secret provider data (secret URI, login-server, tags) via
get_provider_data(); the charm then fetches the secret content withget_secretand validates it withTailscaleCredentials.model_validate. The requirer never supplies data on the wire.Initialize the TailscaleCredentialsRequirer.
- Parameters:
relation_mapping – The charm’s RelationMapping (typically self.model.relations).
app – This application.
relation_name – The name of the relation.
- get_provider_data(relation: ops.Relation) ProviderAppData | None¶
Read and validate the provider’s non-secret app data for the relation.
- Parameters:
relation – A specific relation instance.
- Returns:
A
ProviderAppData(secret URI + login-server + tags) if available and valid, elseNone.
- is_ready(relation: ops.Relation) bool¶
Check whether the provider has published a usable credential.
- Parameters:
relation – A specific relation instance.
- Returns:
True if provider data is present with both
secret_idandlogin_serverset.
- property relations: list[ops.Relation]¶
Return the relation instances for the monitored relation.
- canonical_service_mesh.interfaces.tailscale_credentials.DEFAULT_RELATION_NAME = 'tailscale-credentials'¶