Documentation
¶
Overview ¶
Package config loads and validates the proxy YAML configuration. Use Load to parse from an io.Reader or LoadFile to read from disk. Both expand ${VAR} references using the process environment before unmarshalling.
Index ¶
- Variables
- func NewAllowlist(c *Config) services.Allowlist
- type APITranslations
- type AuthConfig
- type CloudAPI
- type Config
- type ConfigParams
- type CredentialConfig
- type Encryption
- type ExtensionServer
- type ExtensionServerList
- type ExternalAuthConfig
- type JWKSConfig
- type KeyPolicy
- type ListenConfig
- type Metrics
- type NamespaceConfig
- type NamespaceMapping
- type NamespaceRules
- type Routing
- type RoutingMatch
- type RoutingRule
- type Services
- type StaticCredentialConfig
- type StaticTokenConfig
- type TLSConfig
- type Upstream
- type UpstreamList
Constants ¶
This section is empty.
Variables ¶
var ConfigFileTag = fx.ResultTags(`name:"configFile"`)
var Module = fx.Option(fx.Provide( func(p ConfigParams) (*Config, error) { return LoadFile(p.File) }, NewAllowlist, ))
Module is an fx module that provides *Config by loading the file path supplied as the named value "configFile", along with the allowlist derived from it via NewAllowlist, which is the single owner of how the allowlist is built.
Functions ¶
func NewAllowlist ¶ added in v0.5.0
NewAllowlist builds the services.Allowlist admitting the services c allows. It is the fx provider Module registers, and is exported so an application that assembles a partial graph (supplying a *Config rather than loading one) can provide the allowlist the same way rather than rebuilding it.
Types ¶
type APITranslations ¶ added in v0.6.0
type APITranslations struct {
CloudAPI CloudAPI `yaml:"cloudApi"`
}
APITranslations configures rewriting a method an upstream does not serve into the one that does. It is optional and usually absent, and it carries no switch: whether a method is translated is derived from the rest of the configuration rather than declared.
What derives it is Routing.NamespacelessUpstream. The methods Temporal Cloud does not serve on a namespace endpoint are the ones carrying no namespace, so they land on the upstream serving namespace-less requests, and that upstream being Cloud is both necessary and sufficient for a translation to be reachable. An operator who wants the untranslated failure back routes those requests at a Temporal Service that serves them, which is the same statement made where it belongs.
This block exists for the two things detection cannot know: which Cloud environment the control plane lives in, and the API key an mTLS upstream has none of to inherit.
The zero value is the block an operator did not write, which is what almost every configuration has, and it answers for the default - so nothing here is a pointer and no caller has to check before asking. The same holds for CloudAPI.
func (APITranslations) Validate ¶ added in v0.6.0
func (t APITranslations) Validate() error
Validate checks the Cloud API override as it will be dialled. An override nobody wrote is the zero one, which describes the defaults and passes.
type AuthConfig ¶
type AuthConfig struct {
External *ExternalAuthConfig `yaml:"external"`
StaticToken *StaticTokenConfig `yaml:"staticToken"`
JWKS *JWKSConfig `yaml:"jwks"`
}
AuthConfig configures inbound authentication for the proxy listener. Exactly one authenticator must be selected: one of the built-in ones, or an external extension server that decides on the proxy's behalf.
func (*AuthConfig) Validate ¶
func (a *AuthConfig) Validate() error
Validate requires exactly one authenticator and checks the selected one.
type CloudAPI ¶ added in v0.6.0
type CloudAPI struct {
Listen ListenConfig `yaml:",inline"`
Credentials *CredentialConfig `yaml:"credentials"`
}
CloudAPI overrides how the proxy reaches Temporal Cloud's control plane, which answers the methods Cloud does not serve on a namespace frontend.
The block is optional and usually absent. An upstream that Upstream.IsCloud recognizes gets method translation on its own, over a connection to cloud.APIHostPort carrying that upstream's credentials - the same API key authorizes both, so there is nothing more to say.
It is required in one case. The Cloud Ops API accepts an API key only; unlike a namespace frontend it does not accept mTLS. An upstream authenticating with a client certificate therefore has no credential to inherit, and must name an API key here or its translated methods are refused. Beyond that, configure this only to reach a different Cloud environment.
See https://docs.temporal.io/ops.
It is deliberately not an entry in Upstreams: an upstream is a server - a socket, a proxy.Server, and a routing destination - and the control plane is only ever a client connection. Declaring it there would give it three things it cannot use and one it should not have: routability.
func (CloudAPI) IsSaasAPI ¶ added in v0.6.0
IsSaasAPI reports whether the configured control plane addresses Temporal Cloud's own API rather than somewhere else. It is false only when an operator pointed the block elsewhere, which is legitimate for a test double or a private environment, so callers report it rather than reject it.
func (CloudAPI) IsZero ¶ added in v0.6.0
IsZero reports whether the override says nothing at all, which is what an absent block leaves behind. Callers use it to tell a configuration that asked for something from one that never mentioned it.
func (CloudAPI) Upstream ¶ added in v0.6.0
Upstream renders the control plane as an Upstream for the Cloud upstream src, so its connection is dialled by the same resolver, TLS, and credential machinery as any other rather than by a second code path. A nil receiver is the unconfigured case and yields the inherited defaults, so callers need not branch on whether the block is present.
Credentials are inherited from src because a Temporal Cloud API key authorizes the control plane as well as the frontend. TLS is not: the control plane is a different host, so src's server name or client certificate would not apply to it, and the dial default stands instead - verification against the system root pool, which is what the real control plane presents.
When this block is present its tls and insecure are authoritative, the same way they are on an upstream: an absent tls still verifies against the system roots, and plaintext has to be asked for. That is only reachable for a control plane with no credentials, since Validate rejects credentials on an insecure hop, so a key still cannot be sent in the clear.
The name is derived from src rather than fixed, so two Cloud upstreams with different credentials get distinct connections instead of sharing whichever was dialled first.
func (CloudAPI) Validate ¶ added in v0.6.0
Validate checks the control plane as it will actually be dialled, by validating the Upstream it renders to. That covers the same ground as any upstream - dial target, outbound TLS, credentials, and credentials requiring TLS - without restating the rules, and checks the effective configuration (the defaulted address included) rather than only the fields an operator supplied.
An address that is not a Cloud endpoint is not rejected. Nothing but a Cloud deployment serves CloudService, but a test double or a private environment legitimately does not carry the Cloud domain, and the proxy has no way to tell that apart from a typo. It is reported at startup instead, which mirrors how a namespace Cloud would reject is handled for a templated upstream.
type Config ¶
type Config struct {
Listen ListenConfig `yaml:",inline"`
APITranslations APITranslations `yaml:"apiTranslations"`
AllowedServices Services `yaml:"allowedServices"`
Auth *AuthConfig `yaml:"auth"`
Encryption Encryption `yaml:"encryption"`
ExtensionServers ExtensionServerList `yaml:"extensionServers"`
Metrics Metrics `yaml:"metrics"`
Routing Routing `yaml:"routing"`
Upstreams UpstreamList `yaml:"upstreams"`
}
Config is the top-level proxy configuration.
func Load ¶
Load reads and parses the YAML config specified in the Reader. Values of the form ${VAR} are replaced with the corresponding environment variable. A config that names no allowed services gets the default set, and one that leaves a metrics field empty gets that field's default.
func LoadFile ¶
LoadFile reads and parses the YAML config file at path. Values of the form ${VAR} are replaced with the corresponding environment variable.
func (*Config) Validate ¶
Validate requires at least one upstream, checks the listen configuration and every upstream, requires upstream names to be unique, and checks that every cross-reference names something configured: routing references an upstream, while encryption key URIs and external authentication reference an extension server. A missing upstream surfaces on the "upstreams" field. Failures are stamped with the failing node's YAML path as the subject (e.g. "upstreams[0].namespaces.rules.overrides[1]"). A duplicate name surfaces on the "upstreams[name]" field, an unknown routing reference on the "routing"/"routing.rules[i]" subject, and an unknown extension server on the referring "encryption.*" or "auth.external" subject.
type ConfigParams ¶
ConfigParams holds the fx-injected dependencies for loading the config file.
type CredentialConfig ¶
type CredentialConfig struct {
Static *StaticCredentialConfig `yaml:"static"`
}
CredentialConfig configures the credential the proxy presents to an upstream. Static is the only variant today.
func (*CredentialConfig) Validate ¶
func (c *CredentialConfig) Validate() error
Validate requires the static credential and checks it.
type Encryption ¶
type Encryption struct {
Enabled bool `yaml:"enabled"`
CacheSize *int `yaml:"cacheSize"`
Default *KeyPolicy `yaml:"default"`
Overrides map[string]KeyPolicy `yaml:"overrides"`
}
Encryption configures envelope encryption of payloads. When Enabled, a Default key policy is required and governs how DEKs are provisioned and rotated. CacheSize bounds the in-memory DEK cache; see Encryption.DEKCacheSize for what an absent one means. Overrides maps a namespace to a key policy that supersedes Default for that namespace; the keys are pre-translation (local) namespace names, matching the namespace the vault seals under at request time.
func (*Encryption) DEKCacheSize ¶ added in v0.6.0
func (e *Encryption) DEKCacheSize() int
DEKCacheSize is the DEK cache size to apply: the configured size when there is one, and crypto.DefaultCacheSize when the field is absent. Zero disables the cache, so every Open unwraps its DEK through the KEK.
The distinction is why the field is a pointer. Zero is a meaningful value here and a plain int cannot tell an operator who wrote nothing from one who wrote zero - so an absent field would read as "disable the cache" and silently override the vault's own default, turning every payload the proxy opens into a KMS round trip. Absent means "no opinion", and disabling the cache has to be written down.
func (*Encryption) Validate ¶
func (e *Encryption) Validate() error
Validate requires a non-negative cache size, a Default policy whenever encryption is Enabled, and (when a Default is present at all) that the policy itself is valid.
type ExtensionServer ¶ added in v0.3.0
type ExtensionServer struct {
Name string `yaml:"name"`
Listen ListenConfig `yaml:",inline"`
Credentials *CredentialConfig `yaml:"credentials"`
}
ExtensionServer addresses an operator-run gRPC server implementing the extension APIs under api/, currently api.kms.v1.EncryptionService, the pluggable Key Encryption Key provider. The proxy has built-in KMS providers (awskms, azurekeyvault, gcpkms); an extension server is how an operator plugs in a backend the proxy does not support natively, such as an on-prem HSM or an internal key service.
Name identifies the server within the configuration so other blocks can reference it, and must be unique across the list. Credentials, when set, attach per-request credentials to the outbound calls and require TLS, since sending them over a plaintext connection would expose them on the wire.
Unlike Upstream, an extension server is dialed at a fixed address rather than resolved per request, so a templated hostPort is rejected outright instead of being deferred to request time.
func (*ExtensionServer) Validate ¶ added in v0.3.0
func (s *ExtensionServer) Validate() error
Validate checks a single extension server: a name is required, hostPort must be a literal host:port with no template action, and any TLS block must be valid for dialing out. Credentials over an insecure connection are rejected, as is asking for insecure while supplying TLS material. Failures are unattributed, leaving the caller to stamp the path - ExtensionServerList supplies the index.
type ExtensionServerList ¶ added in v0.3.0
type ExtensionServerList []ExtensionServer
ExtensionServerList is the configured set of extension servers. It exists as a named type so the checks that span the whole collection - name and address uniqueness - live alongside the per-entry checks instead of in the parent Config.
func (ExtensionServerList) Validate ¶ added in v0.3.0
func (sl ExtensionServerList) Validate() error
Validate checks every entry and enforces that names and addresses are unique across the list: two servers sharing a name would make a reference ambiguous, and two sharing an address is a copy-paste error rather than a useful configuration. Uniqueness failures are reported on a "[name]"/"[hostPort]" field because they belong to the collection rather than to any one entry, while per-entry failures are stamped with a "[i]" subject. Both compose onto the parent's path, so Config surfaces them as "extensionServers[name]" and "extensionServers[0]". An empty or nil list is valid.
type ExternalAuthConfig ¶ added in v0.4.0
type ExternalAuthConfig struct {
Name string `yaml:"name"`
CredentialHeaders []string `yaml:"credentialHeaders"`
}
ExternalAuthConfig delegates the inbound decision to an extension server implementing api.auth.v1.AuthService, for identity systems the built-in authenticators do not cover.
Name selects which configured extension server to ask. CredentialHeaders names the metadata headers carrying the caller's credentials, which the proxy lifts into the request it sends that server and removes from the stream it forwards upstream. It has to be declared because a verdict reports only admit-or-deny, so nothing in the exchange reveals which headers mattered.
Leaving it empty does not hide the caller's credentials from the server. The proxy forwards the caller's metadata on the call either way, so the server still sees whatever headers the caller sent; what it loses is the request field naming them, so it has to know which metadata to read and cannot tell a header this proxy vouches for from any other. Nothing is stripped before proxying upstream either, so the caller's credential continues to the upstream alongside any credential configured for it.
func (*ExternalAuthConfig) Validate ¶ added in v0.4.0
func (c *ExternalAuthConfig) Validate() error
Validate requires the extension server name; the credential header list may be empty. Whether a server by that name is actually configured is checked by [AuthConfig.referentialRules], which runs where the server list is known.
type JWKSConfig ¶
type JWKSConfig struct {
URL string `yaml:"url"`
Audiences []string `yaml:"audiences"`
Issuer string `yaml:"issuer"`
Header string `yaml:"header"`
Scheme string `yaml:"scheme"`
}
JWKSConfig verifies an inbound JWT's signature and claims against a JWKS.
func (*JWKSConfig) Validate ¶
func (c *JWKSConfig) Validate() error
Validate requires a syntactically valid absolute JWKS URL.
type KeyPolicy ¶
type KeyPolicy struct {
URI url.URL `yaml:"uri"`
DecryptURIs []url.URL `yaml:"decryptURIs"`
Duration time.Duration `yaml:"duration"`
RenewBefore time.Duration `yaml:"renewBefore"`
}
KeyPolicy describes the KMS key backing a DEK and its rotation schedule. URI is the primary key used to wrap new DEKs; DecryptURIs are additional keys accepted when unwrapping existing DEKs (for example, during key migration). Duration is a DEK's lifetime and RenewBefore is the lead time before that lifetime elapses at which rotation begins.
type ListenConfig ¶
type ListenConfig struct {
HostPort string `yaml:"hostPort"`
Insecure bool `yaml:"insecure"`
TLS *TLSConfig `yaml:"tls"`
}
ListenConfig defines properties for a dial or listen target. TLS is the default for anything the proxy dials: with no TLS block the peer is verified against the system root pool, and Insecure is how an operator deliberately asks for plaintext. A listener has no such default, since it has no certificate to present, so it stays plaintext until a TLS block supplies one.
func (*ListenConfig) Dialer ¶ added in v0.6.0
func (l *ListenConfig) Dialer() *creds.Dialer
Dialer resolves the outbound (client) credential for this target. Insecure yields plaintext; otherwise the TLS block decides, and an absent one verifies the peer against the system root pool.
func (*ListenConfig) Listener ¶ added in v0.6.0
func (l *ListenConfig) Listener() *creds.Listener
Listener resolves the inbound (server) credential for this target. A listener has no certificate to fall back on, so it serves plaintext until a TLS block supplies one.
func (*ListenConfig) Validate ¶
func (l *ListenConfig) Validate() error
Validate checks the host:port and, when present, the TLS configuration.
type Metrics ¶ added in v0.6.0
Metrics configures the Prometheus endpoint. HostPort is the address the /metrics handler listens on, and Namespace is the prefix stamped onto every collector: a Prometheus namespace, unrelated to a Temporal namespace. Load defaults both, so neither is empty in a loaded config.
type NamespaceConfig ¶
type NamespaceConfig struct {
Rules NamespaceRules `yaml:"rules"`
}
NamespaceConfig groups the namespace translation rules for an upstream.
func (*NamespaceConfig) Validate ¶
func (c *NamespaceConfig) Validate() error
Validate checks the namespace translation rules.
type NamespaceMapping ¶
NamespaceMapping is one explicit local/remote namespace pair, used to short-circuit the prefix/suffix rule for namespaces whose names do not follow the convention.
func (*NamespaceMapping) Validate ¶
func (m *NamespaceMapping) Validate() error
Validate requires both the local and remote namespace names.
type NamespaceRules ¶
type NamespaceRules struct {
Prefix string `yaml:"prefix"`
Suffix string `yaml:"suffix"`
Overrides []NamespaceMapping `yaml:"overrides"`
// contains filtered or unexported fields
}
NamespaceRules translates namespace names between the local view that workers use and the remote names registered on the upstream Temporal Service.
The default translation is to wrap or unwrap a Prefix and Suffix: Remote("payments") returns Prefix+"payments"+Suffix, and Local of that returns "payments". When an explicit Overrides entry matches, the override takes precedence over the prefix/suffix rule.
func (*NamespaceRules) Configured ¶
func (r *NamespaceRules) Configured() bool
Configured reports whether the rules translate anything. When false the prefix, suffix, and overrides are all empty and Remote and Local are identity, so callers can skip installing translation entirely.
func (*NamespaceRules) Local ¶
func (r *NamespaceRules) Local(remoteNS string) string
Local returns the local namespace name that corresponds to remoteNS. If an override matches it wins; otherwise the configured Prefix and Suffix are stripped from remoteNS.
func (*NamespaceRules) Remote ¶
func (r *NamespaceRules) Remote(localNS string) string
Remote returns the remote namespace name that corresponds to localNS. If an override matches it wins; otherwise localNS is wrapped with the configured Prefix and Suffix.
func (*NamespaceRules) UnmarshalYAML ¶
func (r *NamespaceRules) UnmarshalYAML(unmarshal func(any) error) error
func (*NamespaceRules) Validate ¶
func (r *NamespaceRules) Validate() error
Validate checks that override entries are complete and that no local or remote name is mapped more than once.
type Routing ¶
type Routing struct {
DefaultUpstream string `yaml:"default"`
SystemUpstream string `yaml:"system"`
Rules []RoutingRule `yaml:"rules"`
}
Routing selects which upstream serves a request. DefaultUpstream is the fallback when no rule matches and SystemUpstream serves system-namespace traffic; both name an upstream and are optional. Rules are evaluated in order against the incoming request.
func (*Routing) NamespacelessUpstream ¶ added in v0.6.0
NamespacelessUpstream returns the upstream a request carrying no namespace lands on when no rule claims it: the system upstream when one is named, and the default upstream otherwise. It mirrors what [router.Mux] does with such a request, so a caller deciding what to install for that destination and the router deciding where to send it cannot disagree.
A rule can claim a namespace-less request too - an empty rule namespace matches every namespace, the empty one included - so this names the destination such a request falls through to rather than the only one it can reach. It is empty when neither is configured, which Config.Validate treats as an unroutable namespace-less request rather than an error.
type RoutingMatch ¶
type RoutingMatch struct {
Namespace string `yaml:"namespace"`
Metadata map[string]string `yaml:"metadata"`
}
RoutingMatch describes the request attributes a rule matches on. A match requires at least one of Namespace or Metadata: an empty match would apply to every request, which is what DefaultUpstream is for.
func (*RoutingMatch) Validate ¶
func (m *RoutingMatch) Validate() error
Validate requires at least one of Namespace or Metadata to be set.
type RoutingRule ¶
type RoutingRule struct {
Upstream string `yaml:"upstream"`
Match RoutingMatch `yaml:"match"`
}
RoutingRule sends every request matched by Match to the named Upstream.
func (*RoutingRule) Validate ¶
func (r *RoutingRule) Validate() error
Validate requires the referenced upstream and checks the match.
type Services ¶ added in v0.5.0
type Services []string
Services is the set of gRPC services the proxy is allowed to forward, each named by its proto full name (e.g. "temporal.api.workflowservice.v1.WorkflowService").
func (Services) Allowed ¶ added in v0.5.0
Allowed returns the services to forward: the configured names, or the default set when none were configured. Callers build their allowlist from this rather than from the raw field, so a Config assembled in code rather than through Load forwards the defaults instead of denying everything.
type StaticCredentialConfig ¶
type StaticCredentialConfig struct {
APIKey string `yaml:"apiKey"`
Header string `yaml:"header"`
Scheme string `yaml:"scheme"`
}
StaticCredentialConfig injects a fixed API key as a bearer header on every outbound request to the upstream.
func (*StaticCredentialConfig) Validate ¶
func (c *StaticCredentialConfig) Validate() error
Validate requires the API key.
type StaticTokenConfig ¶
type StaticTokenConfig struct {
Token string `yaml:"token"`
Header string `yaml:"header"`
Scheme string `yaml:"scheme"`
}
StaticTokenConfig compares an inbound bearer token against a fixed value.
func (*StaticTokenConfig) Validate ¶
func (c *StaticTokenConfig) Validate() error
Validate requires the token value.
type TLSConfig ¶
type TLSConfig struct {
CA string `yaml:"ca"` // PEM-encoded CA certificate
Cert string `yaml:"cert"` // PEM-encoded certificate to present
Key string `yaml:"key"` // PEM-encoded private key
ServerName string `yaml:"serverName"` // Optional SNI override, when dialing
}
TLSConfig specifies the TLS material for one target, and reads differently by direction: a listener presents Cert and Key, and a CA additionally requires each client to present a certificate signed by it (mutual TLS), while a dialer verifies its peer against a CA rather than the system roots and presents Cert and Key only for mutual TLS.
NB: Be sure to set ServerName when the host name you dial doesn't match the CN or SAN on the server's certificate.
type Upstream ¶
type Upstream struct {
Name string `yaml:"name"`
Cloud bool `yaml:"cloud"`
Listen ListenConfig `yaml:",inline"`
Namespaces NamespaceConfig `yaml:"namespaces"`
Credentials *CredentialConfig `yaml:"credentials"`
}
Upstream describes a single upstream Temporal Service the proxy connects workers to, along with the configuration for reaching it. Name identifies the upstream so routing rules can refer to it; it must be unique within the config.
Cloud declares the upstream to be Temporal Cloud, which turns on Cloud-specific namespace rules. It is only needed for an address cloud.IsEndpoint does not recognize, such as a private-link hostname; a .tmprl.cloud address is detected without it.
The proxy dials an upstream over TLS unless Listen says otherwise, so a plaintext upstream must set its Insecure field.
func (*Upstream) IsCloud ¶ added in v0.6.0
IsCloud reports whether the upstream is Temporal Cloud, either because it says so or because its address is a Cloud endpoint. The TLS server name counts too: a private-link upstream reaches Cloud through a per-VPC hostname but still pins Cloud's certificate.
func (*Upstream) IsTemplated ¶
IsTemplated reports whether the upstream must be resolved per request because its hostPort, or its TLS server name when one is configured, contains a text/template action.
type UpstreamList ¶ added in v0.3.0
type UpstreamList []Upstream
func (UpstreamList) Validate ¶ added in v0.3.0
func (ul UpstreamList) Validate() error