diff --git a/cmd/dex/config.go b/cmd/dex/config.go index 8861ddef..3f51d95e 100644 --- a/cmd/dex/config.go +++ b/cmd/dex/config.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "encoding/base64" "encoding/json" "fmt" @@ -23,6 +24,15 @@ import ( "github.com/dexidp/dex/storage/sql" ) +func configUnmarshaller(b []byte, v interface{}) error { + if featureflags.ConfigDisallowUnknownFields.Enabled() { + return json.Unmarshal(b, v) + } + dec := json.NewDecoder(bytes.NewReader(b)) + dec.DisallowUnknownFields() + return dec.Decode(v) +} + // Config is the config format for the main application. type Config struct { Issuer string `json:"issuer"` @@ -109,7 +119,7 @@ func (p *password) UnmarshalJSON(b []byte) error { HashFromEnv string `json:"hashFromEnv"` Groups []string `json:"groups"` } - if err := json.Unmarshal(b, &data); err != nil { + if err := configUnmarshaller(b, &data); err != nil { return err } *p = password(storage.Password{ @@ -333,7 +343,7 @@ func (s *Storage) UnmarshalJSON(b []byte) error { Type string `json:"type"` Config json.RawMessage `json:"config"` } - if err := json.Unmarshal(b, &store); err != nil { + if err := configUnmarshaller(b, &store); err != nil { return fmt.Errorf("parse storage: %v", err) } f, ok := storages[store.Type] @@ -346,7 +356,7 @@ func (s *Storage) UnmarshalJSON(b []byte) error { data := []byte(store.Config) if featureflags.ExpandEnv.Enabled() { var rawMap map[string]interface{} - if err := json.Unmarshal(store.Config, &rawMap); err != nil { + if err := configUnmarshaller(store.Config, &rawMap); err != nil { return fmt.Errorf("unmarshal config for env expansion: %v", err) } @@ -363,7 +373,7 @@ func (s *Storage) UnmarshalJSON(b []byte) error { data = expandedData } - if err := json.Unmarshal(data, storageConfig); err != nil { + if err := configUnmarshaller(data, storageConfig); err != nil { return fmt.Errorf("parse storage config: %v", err) } } @@ -462,7 +472,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error { Config json.RawMessage `json:"config"` } - if err := json.Unmarshal(b, &conn); err != nil { + if err := configUnmarshaller(b, &conn); err != nil { return fmt.Errorf("parse connector: %v", err) } f, ok := server.ConnectorsConfig[conn.Type] @@ -475,7 +485,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error { data := []byte(conn.Config) if featureflags.ExpandEnv.Enabled() { var rawMap map[string]interface{} - if err := json.Unmarshal(conn.Config, &rawMap); err != nil { + if err := configUnmarshaller(conn.Config, &rawMap); err != nil { return fmt.Errorf("unmarshal config for env expansion: %v", err) } @@ -492,7 +502,7 @@ func (c *Connector) UnmarshalJSON(b []byte) error { data = expandedData } - if err := json.Unmarshal(data, connConfig); err != nil { + if err := configUnmarshaller(data, connConfig); err != nil { return fmt.Errorf("parse connector config: %v", err) } } diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go index 54d150af..43cb8999 100644 --- a/cmd/dex/serve.go +++ b/cmd/dex/serve.go @@ -97,10 +97,16 @@ func runServe(options serveOptions) error { } var c Config - if err := yaml.Unmarshal(configData, &c); err != nil { + + jsonConfigData, err := yaml.YAMLToJSON(configData) + if err != nil { return fmt.Errorf("error parse config file %s: %v", configFile, err) } + if err := configUnmarshaller(jsonConfigData, &c); err != nil { + return fmt.Errorf("error unmarshalling config file %s: %v", configFile, err) + } + applyConfigOverrides(options, &c) logger, err := newLogger(c.Logger.Level, c.Logger.Format) diff --git a/pkg/featureflags/set.go b/pkg/featureflags/set.go index d4cdf4d8..65a04d8a 100644 --- a/pkg/featureflags/set.go +++ b/pkg/featureflags/set.go @@ -14,4 +14,7 @@ var ( // ContinueOnConnectorFailure allows the server to start even if some connectors fail to initialize. ContinueOnConnectorFailure = newFlag("continue_on_connector_failure", true) + + // ConfigDisallowUnknownFields enables to forbid unknown fields in the config while unmarshaling. + ConfigDisallowUnknownFields = newFlag("config_disallow_unknown_fields", false) ) diff --git a/storage/etcd/config.go b/storage/etcd/config.go index a8aee39a..b4850f3a 100644 --- a/storage/etcd/config.go +++ b/storage/etcd/config.go @@ -15,10 +15,10 @@ var defaultDialTimeout = 2 * time.Second // SSL represents SSL options for etcd databases. type SSL struct { - ServerName string `json:"serverName" yaml:"serverName"` - CAFile string `json:"caFile" yaml:"caFile"` - KeyFile string `json:"keyFile" yaml:"keyFile"` - CertFile string `json:"certFile" yaml:"certFile"` + ServerName string `json:"serverName"` + CAFile string `json:"caFile"` + KeyFile string `json:"keyFile"` + CertFile string `json:"certFile"` } // Etcd options for connecting to etcd databases. @@ -26,11 +26,11 @@ type SSL struct { // configure an etcd namespace either via Namespace field or using `etcd grpc-proxy // --namespace=` type Etcd struct { - Endpoints []string `json:"endpoints" yaml:"endpoints"` - Namespace string `json:"namespace" yaml:"namespace"` - Username string `json:"username" yaml:"username"` - Password string `json:"password" yaml:"password"` - SSL SSL `json:"ssl" yaml:"ssl"` + Endpoints []string `json:"endpoints"` + Namespace string `json:"namespace"` + Username string `json:"username"` + Password string `json:"password"` + SSL SSL `json:"ssl"` } // Open creates a new storage implementation backed by Etcd diff --git a/storage/sql/config.go b/storage/sql/config.go index 222b263a..602e7f8a 100644 --- a/storage/sql/config.go +++ b/storage/sql/config.go @@ -78,7 +78,7 @@ type SSL struct { type Postgres struct { NetworkDB - SSL SSL `json:"ssl" yaml:"ssl"` + SSL SSL `json:"ssl"` } // Open creates a new storage implementation backed by Postgres. @@ -206,7 +206,7 @@ func (p *Postgres) open(logger *slog.Logger) (*conn, error) { type MySQL struct { NetworkDB - SSL SSL `json:"ssl" yaml:"ssl"` + SSL SSL `json:"ssl"` // TODO(pborzenkov): used by tests to reduce lock wait timeout. Should // we make it exported and allow users to provide arbitrary params? diff --git a/storage/storage.go b/storage/storage.go index b49e0fd9..b6e4d285 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -149,28 +149,28 @@ type Storage interface { // - Public clients: https://developers.google.com/api-client-library/python/auth/installed-app type Client struct { // Client ID and secret used to identify the client. - ID string `json:"id" yaml:"id"` - IDEnv string `json:"idEnv" yaml:"idEnv"` - Secret string `json:"secret" yaml:"secret"` - SecretEnv string `json:"secretEnv" yaml:"secretEnv"` + ID string `json:"id"` + IDEnv string `json:"idEnv"` + Secret string `json:"secret"` + SecretEnv string `json:"secretEnv"` // A registered set of redirect URIs. When redirecting from dex to the client, the URI // requested to redirect to MUST match one of these values, unless the client is "public". - RedirectURIs []string `json:"redirectURIs" yaml:"redirectURIs"` + RedirectURIs []string `json:"redirectURIs"` // TrustedPeers are a list of peers which can issue tokens on this client's behalf using // the dynamic "oauth2:server:client_id:(client_id)" scope. If a peer makes such a request, // this client's ID will appear as the ID Token's audience. // // Clients inherently trust themselves. - TrustedPeers []string `json:"trustedPeers" yaml:"trustedPeers"` + TrustedPeers []string `json:"trustedPeers"` // Public clients must use either use a redirectURL 127.0.0.1:X or "urn:ietf:wg:oauth:2.0:oob" - Public bool `json:"public" yaml:"public"` + Public bool `json:"public"` // Name and LogoURL used when displaying this client to the end user. - Name string `json:"name" yaml:"name"` - LogoURL string `json:"logoURL" yaml:"logoURL"` + Name string `json:"name"` + LogoURL string `json:"logoURL"` } // Claims represents the ID Token claims supported by the server.