feat(wash): workload env/config/secrets#5117
Merged
Merged
Conversation
9175614 to
7339efb
Compare
ricochet
added a commit
that referenced
this pull request
May 7, 2026
Builds on #5117 to make this example a complete walkthrough of the new `.wash/config.yaml` schema: an `app-defaults` config-source feeds non-sensitive runtime knobs from `config/defaults.env`, an `upstream-credentials` secret-source pulls `UPSTREAM_API_TOKEN` from the developer's shell via `fromEnv`, `workload.environment.{configFrom,secretFrom}` references both, and `workload.allowedHosts` constrains the outbound call. `workload.config` continues to carry the `otel.resource.*` identity attributes. - New cached `AppConfig` reads `OUTBOUND_HOST`, `request_timeout_ms`, and `UPSTREAM_API_TOKEN` from `wasi:config/store::get_all` once on the first request. Defaults (`example.com`, 5s) apply when keys are absent. - `make_outgoing_request` uses the configured URL, applies the configured timeout to first-byte and between-bytes, and attaches `Authorization: Bearer <token>` when the secret is present. Missing-token paths still work so the example is exercisable without setting `UPSTREAM_API_TOKEN`. - New `log_runtime_config_once` emits a single OTel log line at startup listing every `wasi:config` key visible to the component. Keys only since secret values land in this same map under `copy_environment = true` and the schema's contract is that values are never logged. - Outgoing HTTP span name is now low-cardinality `"outgoing http"`; the host moves to the `url.full` attribute. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
2e83733 to
045bb71
Compare
This was referenced May 11, 2026
vados-cosmonic
left a comment
Contributor
There was a problem hiding this comment.
Did a quick round of review -- I personally am OK with this landing and us iterating on it rather than being stuck in review too long!
In particular I think we can iterate a bit if we land this under flag for early experimentation (but honestly maybe that's too much extra ceremony).
Also, I kind of like the idea of workload::config::X and workload::secrets::Y for interacting with different things related to workloads
e4238a7 to
f980925
Compare
jfleitz
previously approved these changes
May 18, 2026
c3cc5ff to
9f38978
Compare
ricochet
added a commit
that referenced
this pull request
May 19, 2026
Builds on #5117 to make this example a complete walkthrough of the new `.wash/config.yaml` schema: an `app-defaults` config-source feeds non-sensitive runtime knobs from `config/defaults.env`, an `upstream-credentials` secret-source pulls `UPSTREAM_API_TOKEN` from the developer's shell via `fromEnv`, `workload.environment.{configFrom,secretFrom}` references both, and `workload.allowedHosts` constrains the outbound call. `workload.config` continues to carry the `otel.resource.*` identity attributes. - New cached `AppConfig` reads `OUTBOUND_HOST`, `request_timeout_ms`, and `UPSTREAM_API_TOKEN` from `wasi:config/store::get_all` once on the first request. Defaults (`example.com`, 5s) apply when keys are absent. - `make_outgoing_request` uses the configured URL, applies the configured timeout to first-byte and between-bytes, and attaches `Authorization: Bearer <token>` when the secret is present. Missing-token paths still work so the example is exercisable without setting `UPSTREAM_API_TOKEN`. - New `log_runtime_config_once` emits a single OTel log line at startup listing every `wasi:config` key visible to the component. Keys only since secret values land in this same map under `copy_environment = true` and the schema's contract is that values are never logged. - Outgoing HTTP span name is now low-cardinality `"outgoing http"`; the host moves to the `url.full` attribute. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
ricochet
added a commit
that referenced
this pull request
May 19, 2026
Builds on #5117 to make this example a complete walkthrough of the new `.wash/config.yaml` schema: an `app-defaults` config-source feeds non-sensitive runtime knobs from `config/defaults.env`, an `upstream-credentials` secret-source pulls `UPSTREAM_API_TOKEN` from the developer's shell via `fromEnv`, `workload.environment.{configFrom,secretFrom}` references both, and `workload.allowedHosts` constrains the outbound call. `workload.config` continues to carry the `otel.resource.*` identity attributes. - New cached `AppConfig` reads `OUTBOUND_HOST`, `request_timeout_ms`, and `UPSTREAM_API_TOKEN` from `wasi:config/store::get_all` once on the first request. Defaults (`example.com`, 5s) apply when keys are absent. - `make_outgoing_request` uses the configured URL, applies the configured timeout to first-byte and between-bytes, and attaches `Authorization: Bearer <token>` when the secret is present. Missing-token paths still work so the example is exercisable without setting `UPSTREAM_API_TOKEN`. - New `log_runtime_config_once` emits a single OTel log line at startup listing every `wasi:config` key visible to the component. Keys only since secret values land in this same map under `copy_environment = true` and the schema's contract is that values are never logged. - Outgoing HTTP span name is now low-cardinality `"outgoing http"`; the host moves to the `url.full` attribute. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
vados-cosmonic
previously approved these changes
May 19, 2026
Adds a `workload:` section to .wash/config.yaml with `environment` (inline + configFrom + secretFrom), opaque `config`, and outbound `allowedHosts`. Top-level `configs:` and `secrets:` carry named sources of three kinds: `inline`, `file:` (.env format), and `fromEnv:`. Field shape mirrors WorkloadDeployment.localResources so the same file can round-trip to a Kubernetes manifest later. Resolution lives in `crates/wash/src/workload.rs`. Secrets get a stricter posture: file paths canonicalized and confined to the project dir (CVE-2025-62725 class), 0600/0400 mode required on Unix, gitignore-aware warning when a secret file resolves inside the repo working tree. Errors and traces reference sources by name only - resolved values never appear in log output. `wash dev` now resolves the workload before deploy and applies the result to the dev component's LocalResources. The wasi:config plugin is constructed with copy_environment=true, so workload env vars surface via both wasi:cli/env and wasi:config/store::get. `workload.config` is injected into the wasi:config interface entry when at least one component in the workload imports it, sidecars included. create_workload is split into a thin I/O wrapper plus a pure build_workload that the unit tests exercise end-to-end. Also fixes a pre-existing gap by honoring `dev.component_path` (lets the dev build command produce its artifact in a different folder than the release build). Fixes #5088 Fixes #5099 Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
Add a `dev.environment` map that wash dev exports into its own process before constructing the host, so plugins and runtime crates that read from std::env (RUST_LOG, OTEL_*, libpq's PG*, ...) pick the values up. Distinct from workload.environment, which is delivered to the component via wasi:cli/env. It is awkward that we have to add it very early in the main process, since otel is initialized before we have a chance to read the config. Also add the first schema tests for the Config struct, pinning the camelCase contract on WorkloadConfig / EnvironmentLayer / ConfigSource so a future refactor that drops a `rename_all` doesn't silently drop user-configured fields. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
Replace `Vec<String>` / `Arc<[String]>` for `allowed_hosts` with a typed `AllowedHost` enum so the runtime hot path matches against parsed variants. The wire format stays as `[String]` on both wash YAML and the proto API; parsing happens once at the conversion boundary. The enum has four variants: `Any` (`*`), `Authority(host[:port])`, `Url(scheme://host[:port][/])`, and `SuffixWildcard(*.host)`. Wildcards require a leading dot (`*.foo`, not `*foo`) so `*com` matching every .com is a parse error. URL form rejects paths beyond bare `/`, query strings, and fragments `allowed_hosts` is a host policy, not a URL policy. Runtime semantics change to fail-closed: empty `allowed_hosts` now denies all outgoing requests (was: allow all). The wash config layer substitutes `[Any]` when `allowedHosts` is omitted from YAML via a serde default, so `wash dev` stays ergonomic for users who haven't thought about egress. Explicit `allowedHosts: []` in YAML stays empty and denies. `DevRouter` now calls `check_allowed_hosts` instead of shortcutting so the same policy is applied across both routers. Split `ConfigSource` and `SecretSource` into distinct types with `resolve` methods so the stricter secret-handling posture (Unix mode `0600`/`0400`, `O_NOFOLLOW` open + `fstat` perm check, in-repo-tree warning) can only be applied to secrets. Drops the `secret: bool` parameter from internal resolution helpers. Secret-file open is now TOCTOU-safe via `O_NOFOLLOW` (final-component symlink swap rejected) + `File::metadata()` (`fstat` on Unix, can't be raced by path-based mode swap between check and read). DynamicConfig and WasiOtelConfig adopt `bon::Builder` for forward-compatible construction. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
Annotate the AllowedHosts field on LocalResources with a regex matching the four accepted forms (`*`, `host[:port]`, `scheme://host[:port][/]`, `*.suffix[:port]`, `scheme://*.suffix[:port][/]`) so K8s admission rejects bad entries before they reach the runtime. Document that empty or absent allowedHosts denies all outgoing requests (fail-closed), matching the runtime's new behavior; users opt into allow-all with `allowedHosts: ["*"]`. Final validation still runs in the runtime. This regex is an admission-time guard, not the source of truth. Regenerated CRDs via `make manifests`. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
- `integration_http_allowed_hosts`: cover the four AllowedHost shapes end-to-end. Add `test_star_any_permits_all` (literal `*` allows everything), `test_url_policy_pins_scheme` (URL form rejects wrong scheme), and flip `test_empty_allowed_hosts_permits_all` → `test_empty_allowed_hosts_denies_all`. - `integration_http_counter` / `integration_http_tls` / `tests/common`: populate `LocalResources.allowed_hosts` with `["example.com"]` since the http-counter fixture makes outgoing calls to example.com and the empty-list default now denies. Encoding the actual target host documents the test's intent precisely. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
The fixture now reports the host's policy decision via its own status (200 allowed, 403 denied, 502 connect/DNS error) rather than forwarding the remote server's status. Forwarding made policy denials look identical to remote 4xx responses — wikipedia returning 403 to anonymous clients was failing `test_star_any_permits_all` whenever egress worked. Swap the test targets to IANA-reserved `example.com`, `example.org`, and `www.example.com` so the three match outcomes (exact, unrelated, subdomain) each have a dedicated route and no third-party rate limits or bot detection are in the loop. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
ricochet
added a commit
that referenced
this pull request
May 22, 2026
Builds on #5117 to make this example a complete walkthrough of the new `.wash/config.yaml` schema: an `app-defaults` config-source feeds non-sensitive runtime knobs from `config/defaults.env`, an `upstream-credentials` secret-source pulls `UPSTREAM_API_TOKEN` from the developer's shell via `fromEnv`, `workload.environment.{configFrom,secretFrom}` references both, and `workload.allowedHosts` constrains the outbound call. `workload.config` continues to carry the `otel.resource.*` identity attributes. - New cached `AppConfig` reads `OUTBOUND_HOST`, `request_timeout_ms`, and `UPSTREAM_API_TOKEN` from `wasi:config/store::get_all` once on the first request. Defaults (`example.com`, 5s) apply when keys are absent. - `make_outgoing_request` uses the configured URL, applies the configured timeout to first-byte and between-bytes, and attaches `Authorization: Bearer <token>` when the secret is present. Missing-token paths still work so the example is exercisable without setting `UPSTREAM_API_TOKEN`. - New `log_runtime_config_once` emits a single OTel log line at startup listing every `wasi:config` key visible to the component. Keys only since secret values land in this same map under `copy_environment = true` and the schema's contract is that values are never logged. - Outgoing HTTP span name is now low-cardinality `"outgoing http"`; the host moves to the `url.full` attribute. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
ricochet
added a commit
that referenced
this pull request
May 22, 2026
Builds on #5117 to make this example a complete walkthrough of the new `.wash/config.yaml` schema: an `app-defaults` config-source feeds non-sensitive runtime knobs from `config/defaults.env`, an `upstream-credentials` secret-source pulls `UPSTREAM_API_TOKEN` from the developer's shell via `fromEnv`, `workload.environment.{configFrom,secretFrom}` references both, and `workload.allowedHosts` constrains the outbound call. `workload.config` continues to carry the `otel.resource.*` identity attributes. - New cached `AppConfig` reads `OUTBOUND_HOST`, `request_timeout_ms`, and `UPSTREAM_API_TOKEN` from `wasi:config/store::get_all` once on the first request. Defaults (`example.com`, 5s) apply when keys are absent. - `make_outgoing_request` uses the configured URL, applies the configured timeout to first-byte and between-bytes, and attaches `Authorization: Bearer <token>` when the secret is present. Missing-token paths still work so the example is exercisable without setting `UPSTREAM_API_TOKEN`. - New `log_runtime_config_once` emits a single OTel log line at startup listing every `wasi:config` key visible to the component. Keys only since secret values land in this same map under `copy_environment = true` and the schema's contract is that values are never logged. - Outgoing HTTP span name is now low-cardinality `"outgoing http"`; the host moves to the `url.full` attribute. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
ricochet
added a commit
that referenced
this pull request
May 27, 2026
Builds on #5117 to make this example a complete walkthrough of the new `.wash/config.yaml` schema: an `app-defaults` config-source feeds non-sensitive runtime knobs from `config/defaults.env`, an `upstream-credentials` secret-source pulls `UPSTREAM_API_TOKEN` from the developer's shell via `fromEnv`, `workload.environment.{configFrom,secretFrom}` references both, and `workload.allowedHosts` constrains the outbound call. `workload.config` continues to carry the `otel.resource.*` identity attributes. - New cached `AppConfig` reads `OUTBOUND_HOST`, `request_timeout_ms`, and `UPSTREAM_API_TOKEN` from `wasi:config/store::get_all` once on the first request. Defaults (`example.com`, 5s) apply when keys are absent. - `make_outgoing_request` uses the configured URL, applies the configured timeout to first-byte and between-bytes, and attaches `Authorization: Bearer <token>` when the secret is present. Missing-token paths still work so the example is exercisable without setting `UPSTREAM_API_TOKEN`. - New `log_runtime_config_once` emits a single OTel log line at startup listing every `wasi:config` key visible to the component. Keys only since secret values land in this same map under `copy_environment = true` and the schema's contract is that values are never logged. - Outgoing HTTP span name is now low-cardinality `"outgoing http"`; the host moves to the `url.full` attribute. Signed-off-by: Bailey Hayes <bailey@cosmonic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
workload:section to .wash/config.yaml withenvironment(inline + configFrom + secretFrom), opaque
config, and outboundallowedHosts. Top-levelconfigs:andsecrets:carry namedsources of three kinds:
inline,file:(.env format), andfromEnv:. Field shape mirrors WorkloadDeployment.localResourcesso the same file can round-trip to a Kubernetes manifest later.
Resolution lives in
crates/wash/src/workload.rs. Secrets get astricter posture: file paths canonicalized and confined to the
project dir (CVE-2025-62725 class), 0600/0400 mode required on
Unix, gitignore-aware warning when a secret file resolves inside
the repo working tree. Errors and traces reference sources by name
only - resolved values never appear in log output.
wash devnow resolves the workload before deploy and applies theresult to the dev component's LocalResources. The wasi:config
plugin is constructed with copy_environment=true, so workload env
vars surface via both wasi:cli/env and wasi:config/store::get.
workload.configis injected into the wasi:config interface entrywhen at least one component in the workload imports it, sidecars
included.
create_workload is split into a thin I/O wrapper plus a pure
build_workload that the unit tests exercise end-to-end. Also fixes
a pre-existing gap by honoring
dev.component_path(lets the devbuild command produce its artifact in a different folder than the
release build).
Fixes #5088
Fixes #5099