Skip to main content

utic-invocation-settings

Public library for consuming encrypted Unstructured plugin invocation settings — the plugin-side half of the cellular-dataplane "settings in the invoke payload" design. It reads the v1 settings envelope (RSA-OAEP-256-wrapped AES-256-GCM), decrypts inside the plugin at invoke time, and caches only previously authenticated envelopes.

It is deliberately self-contained on cryptography + pydantic — no dependency on any private-feed package — so it can be published to public PyPI and imported by external plugin authors.

Why

Under the cellular dataplane, a shared pod may serve multiple tenants, so a plugin identity decrypts settings routed to that plugin rather than a shared service handing out plaintext. Settings arrive as an opaque ciphertext envelope; this library turns that envelope into a plain settings object, verifying integrity and never logging secrets.

The wire format is frozen in cellular-dataplane/docs/envelope-contract-v1.md. The producer (Secrets Provider / operator) emits exactly that shape; this library is the reference consumer.

Usage

from utic_invocation_settings import (
    TTLCache, decrypt_settings, dimensions, extract_context, extract_envelope,
)

_settings_cache = TTLCache(ttl_seconds=300)     # keyed by full-envelope fingerprint
_key_cache = TTLCache(ttl_seconds=3600)         # keyed by recipient + encryption-key digest

def load_private_key(kid: str):
    # Load the RSA private key for this plugin identity's certificate from the mounted secret.
    ...

def on_invoke(body: dict):
    envelope = extract_envelope(body)            # None only when the caller sent no settings
    context = extract_context(body)              # None only when the caller sent no context

    if envelope is None:
        settings = load_legacy_job_settings_file()        # transitional: older caller
    else:
        settings = decrypt_settings(                      # an envelope that arrived is the
            envelope,                                     # only settings source for this
            private_key_loader=load_private_key,          # request — never fall back here
            settings_cache=_settings_cache,
            key_cache=_key_cache,
        )

    bind_dimensions(dimensions(context))    # e.g. utic-instrumentation; {} for an older caller
    return do_work(settings)

The fallback belongs on the absent branch only. Falling back after a failed decrypt would answer a request configured for one tenant with whatever the pod booted with.

Every failure (unknown format, missing key, RSA/GCM failure, digest mismatch) raises a subclass of InvocationSettingsError — it never returns partial or unverified plaintext.

The two reserved fields

invocation_settings carries what to configure; invocation_context carries who the invocation is for — the identity facets a shared pod can no longer read from its process environment. Both are reserved, out-of-schema fields of the /invoke body, extracted directly from the parsed body rather than declared as handler parameters, so nothing here depends on the serving wrapper.

Both fail closed on a present-but-invalid value: only an absent field signals "older caller, use the legacy path". invocation_context.schema_version is validated, so an incompatible producer surfaces at the first request instead of as quietly missing telemetry.

The context deliberately carries no filesystem paths. Where a plugin scratches to disk is its own implementation detail — tempfile or uuid-named paths under a base the plugin chooses — never caller-supplied input. A caller-supplied write path is a path-traversal surface that then needs bounding, symlink re-checks, and escape tests; not shipping the knob is cheaper than hardening it.

Security and cache notes

  • This is encryption and integrity for the recipient, not producer authentication. A holder of the recipient's public key can create an envelope, so deployments must deliver envelopes over an authenticated control-plane path.
  • expires_at and credential_version are plaintext advisory metadata outside the authenticated header. Use them for scheduling/freshness hints, never authorization decisions.
  • settings_digest is a public, unsalted SHA-256 value and can link identical settings or support low-entropy guess confirmation. Treat envelopes as sensitive metadata.
  • A decrypted-settings cache is keyed by the full authenticated-envelope fingerprint; the AES-key cache is keyed by (kid, encryption_key_digest). clear() is not a revocation barrier for work already decrypting concurrently.

Develop

make install     # uv sync
make test        # unit tests + coverage
make check       # ruff

Note: published to public PyPI on merge to main (see repo README).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

utic_invocation_settings-0.2.1-py3-none-any.whl (18.7 kB view details)

Uploaded Python 3

File details

Details for the file utic_invocation_settings-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for utic_invocation_settings-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1c664fb1694c3b9f57002d076064e28e44d4bcbdef840521811365e9bfac4fbd
MD5 d56ef2cccda43b0f76416aa666c44e07
BLAKE2b-256 cf52ad4761e222728caa73179910ff61df9b678c6fc083d6532cb3e3a55ab6f2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page