# 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](/docs/guides/kubernetes): 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

```bash
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`](/docs/reference/cli), scoped to the
app environment that component should see:

```bash
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:

```bash
wasmcloud --secrets-topic-prefix wasmcloud.secrets
```

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

```bash
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:

```yaml
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:

```bash
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:

```yaml
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:

```bash
wash keys gen curve   # prints an SX… seed
```

```yaml
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](/docs/guides/wasm) 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](/docs/guides/telemetry).
