config

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 17 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
var ConfigFileTag = fx.ResultTags(`name:"configFile"`)
View Source
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

func NewAllowlist(c *Config) services.Allowlist

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

func (c CloudAPI) IsSaasAPI() bool

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

func (c CloudAPI) IsZero() bool

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

func (c CloudAPI) Upstream(src *Upstream) *Upstream

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

func (c CloudAPI) Validate() error

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

func Load(r io.Reader) (*Config, error)

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

func LoadFile(path string) (*Config, error)

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

func (c *Config) Validate() error

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

type ConfigParams struct {
	fx.In
	File string `name:"configFile"`
}

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.

func (*KeyPolicy) Validate

func (p *KeyPolicy) Validate() error

Validate requires a valid primary and decrypt key URIs, a positive Duration, and a RenewBefore in [0, Duration) so rotation is scheduled strictly before a DEK expires. The upper bound mirrors the crypto vault's own invariant.

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

type Metrics struct {
	HostPort  string `yaml:"hostPort"`
	Namespace string `yaml:"namespace"`
}

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.

func (*Metrics) Validate added in v0.6.0

func (m *Metrics) Validate() error

Validate requires a valid host:port and a non-empty namespace. Load defaults both, so a namespace failure is only reachable for a Metrics built directly, and a hostPort failure only for a config that sets one that will not parse.

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

type NamespaceMapping struct {
	Local  string `yaml:"local"`
	Remote string `yaml:"remote"`
}

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

func (r *Routing) NamespacelessUpstream() string

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.

func (*Routing) Validate

func (r *Routing) Validate() error

Validate checks every rule. Per-rule failures are stamped with a "rules[i]" subject. It does not verify that the referenced upstreams exist; that check needs the full set of upstream names and lives in Config.Validate.

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

func (s Services) Allowed() []string

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.

func (*Services) Validate added in v0.5.0

func (s *Services) Validate() error

Validate rejects duplicate entries and any name the proxy cannot forward. An empty list is valid. Failures carry the "allowedServices" field and no subject, so Config nests this under an empty subject rather than restating the name.

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.

func (*TLSConfig) Validate

func (t *TLSConfig) Validate() error

Validate checks the inbound (listener) TLS material. It delegates to the resolved server credential, which owns the mode decision (server TLS vs mutual TLS) and the certificate file checks.

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

func (u *Upstream) IsCloud() bool

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

func (u *Upstream) IsTemplated() bool

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.

func (*Upstream) Validate

func (u *Upstream) Validate() error

Validate checks the upstream name, dial target, and namespace configuration. A templated hostPort (containing a text/template action) is resolved per-request, so it is not checked as a literal host:port here; a static hostPort still is.

type UpstreamList added in v0.3.0

type UpstreamList []Upstream

func (UpstreamList) Validate added in v0.3.0

func (ul UpstreamList) Validate() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL