Problem
Several application settings are secret material today, but they are authored directly in trusted-server.toml and then stored as part of the canonical runtime config payload. This increases plaintext exposure and makes it harder for provisioning to manage secrets safely.
We should establish a first-class secret reference pattern for config fields that should be backed by a provider secret store. The goal is for secret-bearing fields to be clearly marked in code, represented in TOML as references, and optionally generated/provisioned by the CLI instead of requiring users to paste plaintext values into config files.
Current secret-bearing config fields
Current obvious candidates include:
[publisher]
proxy_secret = "..."
[edge_cookie]
secret_key = "..."
[[handlers]]
username = "..."
password = "..."
Notes:
publisher.proxy_secret is secret material.
edge_cookie.secret_key is secret material.
handlers.password is secret material.
handlers.username may be sensitive/redacted, but is not necessarily secret-store material.
- Request-signing private keys are already intended to live in
signing_keys.
- The Fastly runtime API token is already intended to live in
api-keys/api_key, but that location is hardcoded and should become provider-scoped.
- Future integrations may add API keys, client secrets, shared secrets, etc.
Today Redacted<String> helps avoid logging/displaying secret values, but it does not encode the stronger semantic that a value should be sourced from a secret store.
Proposed direction
Introduce a dedicated secret value/reference type for config fields that should be backed by a secret store.
A near-term TOML shape could be:
[providers.fastly.secrets]
default_store_name = "ts_secrets"
[publisher]
domain = "example.com"
cookie_domain = ".example.com"
origin_url = "https://origin.example.com"
proxy_secret = { secret = "publisher/proxy_secret" }
[edge_cookie]
secret_key = { secret = "edge_cookie/secret_key" }
[[handlers]]
id = "admin"
path = "^/admin"
username = "admin"
password = { secret = "handlers/admin/password" }
With a provider default secret store, this:
proxy_secret = { secret = "publisher/proxy_secret" }
means:
provider = fastly
store = ts_secrets
key = publisher/proxy_secret
For advanced cases, a field could override the default store:
proxy_secret = { store = "publisher_secrets", key = "proxy_secret" }
Default secret refs and generation
Each secret-bearing field should have metadata describing:
- config path
- default secret key
- whether the secret can be generated
- generation policy, if any
- whether inline values are allowed during migration/dev
Possible defaults:
| Field |
Default key |
Generate? |
Suggested generator |
publisher.proxy_secret |
publisher/proxy_secret |
yes |
random base64, 32 bytes |
edge_cookie.secret_key |
edge_cookie/secret_key |
yes |
random hex/base64, 32 bytes |
handlers.<id>.password |
handlers/<id>/password |
maybe |
generated password/base64 |
| Fastly runtime API token |
provider-scoped key, e.g. fastly/runtime_api_key or current api_key |
no |
user-provided |
| Request-signing private keys |
key ID based |
yes |
Ed25519 keypair |
The CLI should be able to generate values for fields where users do not need to know the value, such as publisher.proxy_secret and edge_cookie.secret_key.
Handler passwords need extra care because the operator may need to know or distribute the generated password. We may want generation for handler passwords to be opt-in or require explicit confirmation.
Suggested Rust shape
Separate redaction from secret-store semantics.
Current:
Possible future shape:
pub enum SecretString {
Inline(Redacted<String>),
Ref(SecretRef),
DefaultRef,
}
pub struct SecretRef {
pub store: Option<String>,
pub key: String,
}
Config structs could then explicitly mark secret-store-backed fields:
pub struct Publisher {
pub domain: String,
pub cookie_domain: String,
pub origin_url: String,
pub proxy_secret: SecretString,
}
pub struct EdgeCookie {
pub secret_key: SecretString,
}
pub struct Handler {
pub id: Option<String>,
pub path: String,
pub username: Redacted<String>,
pub password: SecretString,
}
Field metadata can start as a manual trait rather than a derive macro:
pub struct SecretFieldDescriptor {
pub path: &'static str,
pub default_key: &'static str,
pub generation: SecretGeneration,
pub required: bool,
}
pub enum SecretGeneration {
None,
RandomBase64 { bytes: usize },
RandomHex { bytes: usize },
}
A derive/annotation approach could be considered later, but a manual descriptor keeps the first implementation simpler.
Runtime resolution model
Use a two-phase settings model:
RawSettings -> contains SecretString refs/default refs/inline values
ResolvedSettings -> contains resolved Redacted<String> values for runtime use
Runtime flow:
load canonical config payload
parse RawSettings
resolve secrets through RuntimeServices.secret_store()
validate resolved settings
serve request with one immutable resolved settings snapshot
Important behavior:
- Runtime should not generate missing secrets.
- Runtime should fail closed when a required secret ref cannot be resolved.
- Canonical app config should not contain secret material.
- Config hashes should not change when secret values rotate, only when refs/config change.
CLI/provisioning behavior
Provisioning should understand secret refs and missing generated secrets.
Possible plan output:
Missing secrets in Fastly secret store `ts_secrets`:
- publisher.proxy_secret -> publisher/proxy_secret
action: generate random base64 32 bytes
- edge_cookie.secret_key -> edge_cookie/secret_key
action: generate random base64 32 bytes
- handlers.admin.password -> handlers/admin/password
action: requires value or explicit --generate-handler-passwords
This could be exposed through dedicated commands later:
ts secrets plan
ts secrets apply
or folded into existing provisioning:
ts provision fastly plan
ts provision fastly apply
The important part is that CLI provisioning should be able to create/populate provider secret stores before uploading the canonical app config.
Migration policy question
We need to decide how to handle inline secrets.
Recommended transitional policy:
- Allow inline secrets during local/dev workflows.
- Make
ts config validate warn when inline secrets are present.
- Make production provisioning warn or reject inline secrets unless explicitly allowed.
- Add an extraction/generation path that uploads secrets and rewrites or canonicalizes config to secret refs.
Longer-term policy could require refs for deployable production config.
Acceptance criteria
Open questions
- Should
SecretString support inline values permanently, or only for migration/dev?
- Should
DefaultRef be represented explicitly in TOML, or should omitted secret fields imply their default refs?
- Should
ts config init emit explicit refs or omit generated-secret fields entirely?
- Should handler passwords be generated by default, opt-in, or never generated?
- Do handlers need a stable
id field so default secret keys are not based on array indexes?
- Should secret refs participate in the application config hash? Secret values should not, but refs likely should.
- Should CLI upload a transformed canonical config with refs without rewriting the local authoring file?
Problem
Several application settings are secret material today, but they are authored directly in
trusted-server.tomland then stored as part of the canonical runtime config payload. This increases plaintext exposure and makes it harder for provisioning to manage secrets safely.We should establish a first-class secret reference pattern for config fields that should be backed by a provider secret store. The goal is for secret-bearing fields to be clearly marked in code, represented in TOML as references, and optionally generated/provisioned by the CLI instead of requiring users to paste plaintext values into config files.
Current secret-bearing config fields
Current obvious candidates include:
Notes:
publisher.proxy_secretis secret material.edge_cookie.secret_keyis secret material.handlers.passwordis secret material.handlers.usernamemay be sensitive/redacted, but is not necessarily secret-store material.signing_keys.api-keys/api_key, but that location is hardcoded and should become provider-scoped.Today
Redacted<String>helps avoid logging/displaying secret values, but it does not encode the stronger semantic that a value should be sourced from a secret store.Proposed direction
Introduce a dedicated secret value/reference type for config fields that should be backed by a secret store.
A near-term TOML shape could be:
With a provider default secret store, this:
means:
For advanced cases, a field could override the default store:
Default secret refs and generation
Each secret-bearing field should have metadata describing:
Possible defaults:
publisher.proxy_secretpublisher/proxy_secretedge_cookie.secret_keyedge_cookie/secret_keyhandlers.<id>.passwordhandlers/<id>/passwordfastly/runtime_api_keyor currentapi_keyThe CLI should be able to generate values for fields where users do not need to know the value, such as
publisher.proxy_secretandedge_cookie.secret_key.Handler passwords need extra care because the operator may need to know or distribute the generated password. We may want generation for handler passwords to be opt-in or require explicit confirmation.
Suggested Rust shape
Separate redaction from secret-store semantics.
Current:
Possible future shape:
Config structs could then explicitly mark secret-store-backed fields:
Field metadata can start as a manual trait rather than a derive macro:
A derive/annotation approach could be considered later, but a manual descriptor keeps the first implementation simpler.
Runtime resolution model
Use a two-phase settings model:
Runtime flow:
Important behavior:
CLI/provisioning behavior
Provisioning should understand secret refs and missing generated secrets.
Possible plan output:
This could be exposed through dedicated commands later:
or folded into existing provisioning:
The important part is that CLI provisioning should be able to create/populate provider secret stores before uploading the canonical app config.
Migration policy question
We need to decide how to handle inline secrets.
Recommended transitional policy:
ts config validatewarn when inline secrets are present.Longer-term policy could require refs for deployable production config.
Acceptance criteria
SecretString/SecretRefconfig representation.providers.fastly.secrets.Open questions
SecretStringsupport inline values permanently, or only for migration/dev?DefaultRefbe represented explicitly in TOML, or should omitted secret fields imply their default refs?ts config initemit explicit refs or omit generated-secret fields entirely?idfield so default secret keys are not based on array indexes?