Documentation
¶
Overview ¶
Package ops hosts chassis-core op handlers that consume the per-tenant secret store. These are computed-secret ops: they read cleartext from op.Secrets (via context), compute a digest or signature in-process, and write the **non-secret derived value** back into the response envelope. The cleartext is consumed once inside the op and goes nowhere else.
See internal docs/todo-secret-store.md §4.2 for the substitution-vs- computation split.
Index ¶
- func BasicAuthEncode(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)
- func BasicAuthVerify(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)
- func Copy(ctx context.Context, _ string, in, _ []byte) (event.Payload, error)
- func HMACSign(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)
- func HMACVerify(ctx context.Context, opName string, in, _ []byte) (event.Payload, error)
- func WebRender(ctx context.Context, _ string, in, _ []byte) (event.Payload, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func BasicAuthEncode ¶
BasicAuthEncode is the handler for `txco://basic-auth-encode`. It produces the `base64(user:password)` value used by HTTP Basic auth (RFC 7617), with the password coming from op.Secrets.
WITH parameters (op.Meta):
secrets.password.secret = "TWILIO_AUTH_TOKEN" // required user = "AC1234567890" // required output_path = "_txc.computed.basic_auth" // default
The handler writes the encoded value (NOT the cleartext) at output_path. Downstream ops set the Authorization header as `Basic <encoded>` — typically via a `secrets.headers.authorization` declaration with `format = "Basic {output_path-value}"`, but since the encoded value is not itself a secret, simpler: just a normal SET that interpolates the envelope value into the header.
Cleartext is consumed inside this function and never leaves it. The base64 encoding is one-way only in the sense that base64- decoding reveals the cleartext — DO NOT trace this output as "non-secret"; it IS a wire credential. The op writes it at _txc.computed.* by default, where the operator chooses how to route it.
func BasicAuthVerify ¶ added in v0.2.25
BasicAuthVerify is the handler for `txco://basic-auth-verify`. It checks an inbound `Authorization: Basic <base64(user:password)>` header against a literal user and a password held in the per-op secret bag, and writes a verdict — never the credential — so a later rule can branch.
It is the inbound counterpart to txco://basic-auth-encode and the same shape as txco://hmac-verify: a txcl `==` cannot see the secret bag and would short-circuit on the first differing byte, and encode's output (the base64 token) is itself the wire credential, which must not be written to the envelope for a comparison and then linger in every step trace. Here the presented user and password are compared in constant time, both halves always evaluated, and the decoded bytes are wiped.
Gate it with a WHEN on the route; it runs only when its rule fires. A rule after it acts on the verdict:
WHEN @computed.basic_auth_ok != true
EMIT @web.res.status = 401,
@web.res.headers.www-authenticate.0 = "Basic realm=provision",
@halt = true
WITH parameters (op.Meta):
user = "provision" // required: the expected user-id secrets.password.secret = "PROVISION_PASSWORD" // required: NAME of the password secret secrets.password.optional = true // the secret MAY be unset (see below) allow_unconfigured = true // unset secret ⇒ ok=true (default false) header_path = "_txc.web.req.headers.Authorization.0" // gjson path to the header value output_path = "_txc.computed.basic_auth_ok" // sjson path for the verdict (bool) configured_path = "_txc.computed.basic_auth_configured" // sjson path: was a password present (bool)
A missing, empty, or malformed header, another scheme, or a wrong user or password is NOT an op error — it is ok=false (fail closed). Config problems (no user, no secret ref, no bag) are errors.
"Unconfigured" is reachable only with `secrets.password.optional = true`: the processor otherwise refuses to run an op whose secret it cannot materialize. Then the rule decides what absence means through `allow_unconfigured` — true is the loopback-demo shape ("open until a password is set"); the default false keeps a route closed when the secret is missing OR misnamed. `basic_auth_configured` says which case produced the verdict, so a log or a trace can tell "let in" from "checked".
func Copy ¶
Copy is the handler for `txco://copy`. It reads a value from one envelope path and writes it to another, with optional encoding.
This is the chassis-side answer to "txcl SET RHS is literal-only" (the constraint that forces `txco://route` to exist in Go). When a rule needs to move an envelope field — e.g. "the response body for this web request lives at .text, please put it at _txc.web.res.body" — Copy is the primitive.
WITH parameters (op.Meta):
from = ".text" (required: source path on input envelope)
to = "_txc.web.res.body" (required: destination path on response)
encode = "base64" (optional: "base64" | "" — default "")
fallback = "value" (optional: literal substituted when
the source path is empty/missing)
Path syntax follows gjson on read (a leading "." is optional and stripped) and sjson on write. When the source path is absent or resolves to an empty value AND `fallback` is set, the literal `fallback` value is used as the source instead — letting one rule express "use query param if present, else fall back to this." (Named `fallback`, not `default`: `default` is a reserved txcl keyword, so it can't be a WITH param name.) Without `fallback`, an empty source produces an empty destination (no failure). A missing `from` or `to` parameter at the WITH level IS an authoring error and fails loud.
func HMACSign ¶
HMACSign is the handler for `txco://hmac-sign`. It HMACs an envelope-path value with a named secret key and writes the digest at a declared output path.
WITH parameters (op.Meta):
secrets.key.secret = "WEBHOOK_HMAC" // required: NAME in op.Secrets algorithm = "sha256" // sha256 (default) | sha512 input_path = "body" // gjson path on op.Input output_path = "_txc.computed.sig" // sjson path on the response encoding = "hex" // hex (default) | base64
The handler reads `op.Secrets["WEBHOOK_HMAC"]` (the splice puts it there during processor.Run), computes `HMAC-<alg>(secret, <input>)` — where <input> is the value at input_path, a JSON string taken as its literal (unquoted) value and an object/array as its raw bytes (see hmacInputBytes; matches hmac-verify so signatures round-trip) — and writes the digest into the response at output_path. The response is JSON; output_path defaults to `_txc.computed.hmac` if absent.
Cleartext is consumed inside this function and never leaves it — the digest is the safe-to-publish artifact (one-way function of input+key).
func HMACVerify ¶
HMACVerify is the handler for `txco://hmac-verify`. It recomputes an HMAC over an envelope-path value with a named secret key and constant-time-compares it to an expected signature carried on the envelope, writing a boolean result at output_path.
It is the verification counterpart to txco://hmac-sign, and it lives as an op (not a txcl function) for two reasons functions can't satisfy: the keyed HMAC needs the per-op secret bag (txcl functions never see it), and the comparison must be constant-time (a txcl `==` short-circuits and leaks timing). This is the primitive behind signed-webhook verification — Stripe / GitHub / Shopify / Slack all use minor variants of HMAC-over-body.
Gate it with a WHEN like any op; it only runs when its rule fires. It adds `sig_valid` to the response so a later rule can branch, e.g.:
WHEN ._txc.computed.sig_valid != true EXEC "txco://..." # reject / 401
WITH parameters (op.Meta):
secrets.key.secret = "STRIPE_WEBHOOK_SECRET" // required: NAME in op.Secrets algorithm = "sha256" // sha256 (default) | sha512 input_path = "verify.signed" // gjson path to the signed data expected_path = "verify.v1" // required: gjson path to the expected sig encoding = "hex" // hex (default) | base64 (of the expected sig) output_path = "_txc.computed.sig_valid" // sjson path for the bool result
String inputs are hashed as their literal (unquoted) value, so a scheme that signs a constructed string like Stripe's "t.body" works; object/array inputs use the raw JSON bytes (preserving whitespace and key order, which is what vendors sign).
Config problems (missing secret ref or expected_path, unmaterialized secret, bad algorithm/encoding) return an error. A missing, empty, or malformed expected signature is NOT an error — it's a failed verification, so the op emits sig_valid=false (fail closed).
The op emits ONLY the boolean — never the recomputed digest, which is itself a valid signature and must not leak into the envelope/trace.
func WebRender ¶
WebRender is the handler for `txco://web-render`. It reads a value from a source envelope path, optionally transforms it (raw / HTML wrap / markdown-to-HTML), base64-encodes it, and writes the result into the web-response shape `_txc.web.res.*` plus `_txc.halt = true`. Designed for the "scope 200 renders the answer that scope 100 produced" pattern.
WITH parameters (op.Meta):
source = ".text" (optional, default ".text")
content_type = "text/plain; charset=utf-8" (optional, default
matches the wrap mode)
status = 200 (optional, default 200)
wrap = "raw" (optional: "raw" |
"html" | "markdown-to-html")
`wrap` modes:
- "raw" — verbatim bytes. Default content-type "text/plain; charset=utf-8".
- "html" — wrap the (HTML-escaped) source in a minimal `<pre>` document. Default content-type "text/html; charset=utf-8".
- "markdown-to-html" — render the source as CommonMark via goldmark. Default content-type "text/html; charset=utf-8".
Types ¶
This section is empty.