Use promo code BETATEST1 for full access
seekrit
Docs/wasmCloud

wasmCloud

wasmCloud is the one WebAssembly host that published a pluggable secrets API on purpose, and its design and seekrit's agree almost line for line. Both believe the transport should be opaque to everything in between, that a workload should be identified by a signed token it cannot forge, and that a value should be handed over as something explicitly revealed rather than scattered through the environment.

So on wasmCloud you do not have to choose between "the platform knows about my secrets" and "the platform never sees them". The seekrit secrets backend runs on your own lattice, holds the service token, decrypts locally, and seals each value to the key of the host that asked for it. Your components ask wasmCloud for secrets exactly as they would with any other backend.

note

This is the same shape as the Kubernetes integration: a small resolver you run, next to the thing that consumes secrets. seekrit operates neither — the decryption happens on infrastructure you control, and the API only ever hands out ciphertext.

Install the backend

helm repo add seekrit https://charts.seekrit.dev
helm install seekrit-wasmcloud seekrit/seekrit-wasmcloud \
  --namespace wasmcloud --create-namespace \
  --set profiles.checkout.token=skt_… \
  --set profiles.checkout.issuers[0]=ACCOUNT_PUBLIC_KEY \
  --set nats.url=nats://nats:4222

The same chart is published as an OCI artifact, if you would rather not add a repository: helm install seekrit-wasmcloud oci://registry-1.docker.io/seekritdev/seekrit-wasmcloud takes the same flags.

Mint the token with seekrit token create, scoped to the app environment that component should see:

seekrit token create --name wasmcloud --app checkout --env production

ACCOUNT_PUBLIC_KEY is the nkeys account that signs your components — wash keys list will show it. What it is for is the next section.

Not on Kubernetes? The backend is a single static binary in a scratch container; run it anywhere it can reach your NATS and the seekrit API.

Point wasmCloud at it

Two steps, and both fail silently — the backend runs, the lattice simply never asks it, with no error naming either side.

1. Hosts must allow the secrets subtree. Start each host with the subject prefix the backend listens under, and make sure its NATS user may publish there:

wasmcloud --secrets-topic-prefix wasmcloud.secrets

2. Each secret needs a config entry naming the backend and the seekrit secret it maps to:

wash config put SECRET_database_url \
  backend=seekrit \
  key=DATABASE_URL \
  type=secret.wasmcloud.dev/v1alpha1

Then reference it from the component in your wadm manifest:

spec:
  components:
    - name: checkout
      type: component
      properties:
        image: ghcr.io/acme/checkout:0.1.0
        secrets:
          - name: database_url
            properties:
              policy: default
              key: DATABASE_URL

Check the backend is reachable from a host's NATS before you deploy anything:

nats req 'wasmcloud.secrets.v1alpha1.seekrit.server_xkey' ''

A public key comes back. A timeout means step 1, step 2, or the NATS permissions — not seekrit.

Who may read what

A wasmCloud secret request carries three identity-ish things, and they are not equally trustworthy. The JWTs are nkeys tokens, which are self-issued: anyone can mint a keypair, sign a token claiming any subject they like, and it will verify. What a signature actually proves is that the holder of the issuer key vouched for that subject. The wadm application name is plain, unsigned metadata.

So profiles anchor on the issuer, live in a file only you control, and match by AND — every list a profile declares must match, so declaring more narrows and never widens:

profiles:
  checkout:
    token: skt_...
    issuers: ["ACCOUNT_PUBLIC_KEY"]   # who signed the component — the anchor
    applications: ["checkout"]        # narrows; unsigned, never the only matcher
  billing:
    token: skt_...
    issuers: ["ACCOUNT_PUBLIC_KEY"]
    entities: ["MCOMPONENTKEY..."]    # narrows to one component

Selection is default-deny. An identity that matches no profile is refused, and one that matches more than one is also refused rather than resolved to either — picking one would hand a workload an environment nobody unambiguously granted it. A profile that declares nothing but applications is rejected when the chart renders, rather than quietly trusted.

Each profile's token is bound to one app environment, so key names a secret within that environment and cannot reach outside it.

Two things it will not do

Both are refused rather than ignored, because a silently dropped constraint hands back a value the caller believes is narrower than it is:

  • version — seekrit's read path has no per-secret versions. Leave it unset, or set it to latest; any other pin is an error.
  • field — a seekrit secret is a single string. Name the secret in key.

Rotation

Each profile re-resolves on a timer (refreshInterval, 60s by default), so a rotated secret reaches components without a redeploy. Startup is fail-closed — a bad token or an unreachable API stops the pod coming up rather than registering a backend that answers "not found" for everything — while refreshes are lenient, keeping the last good snapshot and retrying. That interval is also the bound on the leniency: a revoked token stops working within one tick of the API answering again.

Running more than one replica

replicaCount > 1 requires a fixed server key, and the chart refuses to render without one:

wash keys gen curve   # prints an SX… seed
backend:
  xkeySeed: SX...
replicaCount: 3

The reason is worth knowing even at one replica. Fetching the server's key and fetching a secret are two separate NATS requests over a shared queue group, so they can land on different pods. Without one seed between them each pod advertises its own key, and a request sealed to one pod cannot be opened by another. Setting a seed also means a restart does not invalidate a key a host had already cached.

The other option: decrypt inside the guest

If you would rather the value never exist outside the component's own sandbox, the WASI secrets component is the alternative: your guest imports seekrit:secrets/store and decrypts in its own linear memory. It works on any WASI 0.2 host, wasmCloud included — take the default build for a wasmCloud 1.x host, and the -rc1 build for wash-runtime, which registers a later draft of wasi:config.

The trade-off is what each one integrates with. The component is invisible to the platform — wash and wadm do not know your secrets exist, and the token reaches the guest through link configuration. The backend is the opposite: the value spends a moment in the backend's memory on your infrastructure, and in exchange wasmCloud's own secrets tooling works normally. Most teams on wasmCloud want the backend; reach for the component when the guest is the only thing you are willing to trust.

Telemetry

Both the backend and the chart support OpenTelemetry export to your collector, never to seekrit. Spans carry secret names, the signed identity that asked, and the outcome; never values and never tokens. See Telemetry.