*: switch to github.com/ghodss/yaml for more consistent YAML parsing
ghodss/yaml converts from YAML to JSON before attempting to unmarshal. This allows us to: * Get the correct behavor when decoding base64'd []byte slices. * Use *json.RawMessage. * Not have to support extravagant YAML features. * Let our structs use `json:` tags
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/dex/connector"
|
||||
@@ -18,129 +18,58 @@ import (
|
||||
|
||||
// Config is the config format for the main application.
|
||||
type Config struct {
|
||||
Issuer string `yaml:"issuer"`
|
||||
Storage Storage `yaml:"storage"`
|
||||
Connectors []Connector `yaml:"connectors"`
|
||||
Web Web `yaml:"web"`
|
||||
OAuth2 OAuth2 `yaml:"oauth2"`
|
||||
GRPC GRPC `yaml:"grpc"`
|
||||
Issuer string `json:"issuer"`
|
||||
Storage Storage `json:"storage"`
|
||||
Connectors []Connector `json:"connectors"`
|
||||
Web Web `json:"web"`
|
||||
OAuth2 OAuth2 `json:"oauth2"`
|
||||
GRPC GRPC `json:"grpc"`
|
||||
|
||||
Templates server.TemplateConfig `yaml:"templates"`
|
||||
Templates server.TemplateConfig `json:"templates"`
|
||||
|
||||
// StaticClients cause the server to use this list of clients rather than
|
||||
// querying the storage. Write operations, like creating a client, will fail.
|
||||
StaticClients []storage.Client `yaml:"staticClients"`
|
||||
StaticClients []storage.Client `json:"staticClients"`
|
||||
|
||||
// If enabled, the server will maintain a list of passwords which can be used
|
||||
// to identify a user.
|
||||
EnablePasswordDB bool `yaml:"enablePasswordDB"`
|
||||
EnablePasswordDB bool `json:"enablePasswordDB"`
|
||||
|
||||
// StaticPasswords cause the server use this list of passwords rather than
|
||||
// querying the storage. Cannot be specified without enabling a passwords
|
||||
// database.
|
||||
//
|
||||
// The "password" type is identical to the storage.Password type, but does
|
||||
// unmarshaling into []byte correctly.
|
||||
StaticPasswords []password `yaml:"staticPasswords"`
|
||||
}
|
||||
|
||||
type password struct {
|
||||
Email string `yaml:"email"`
|
||||
Username string `yaml:"username"`
|
||||
UserID string `yaml:"userID"`
|
||||
|
||||
// Because our YAML parser doesn't base64, we have to do it ourselves.
|
||||
//
|
||||
// TODO(ericchiang): switch to github.com/ghodss/yaml
|
||||
Hash string `yaml:"hash"`
|
||||
}
|
||||
|
||||
// decode the hash appropriately and convert to the storage passwords.
|
||||
func (p password) toPassword() (storage.Password, error) {
|
||||
hash, err := base64.StdEncoding.DecodeString(p.Hash)
|
||||
if err != nil {
|
||||
return storage.Password{}, fmt.Errorf("decoding hash: %v", err)
|
||||
}
|
||||
return storage.Password{
|
||||
Email: p.Email,
|
||||
Username: p.Username,
|
||||
UserID: p.UserID,
|
||||
Hash: hash,
|
||||
}, nil
|
||||
StaticPasswords []storage.Password `json:"staticPasswords"`
|
||||
}
|
||||
|
||||
// OAuth2 describes enabled OAuth2 extensions.
|
||||
type OAuth2 struct {
|
||||
ResponseTypes []string `yaml:"responseTypes"`
|
||||
ResponseTypes []string `json:"responseTypes"`
|
||||
// If specified, do not prompt the user to approve client authorization. The
|
||||
// act of logging in implies authorization.
|
||||
SkipApprovalScreen bool `yaml:"skipApprovalScreen"`
|
||||
SkipApprovalScreen bool `json:"skipApprovalScreen"`
|
||||
}
|
||||
|
||||
// Web is the config format for the HTTP server.
|
||||
type Web struct {
|
||||
HTTP string `yaml:"http"`
|
||||
HTTPS string `yaml:"https"`
|
||||
TLSCert string `yaml:"tlsCert"`
|
||||
TLSKey string `yaml:"tlsKey"`
|
||||
HTTP string `json:"http"`
|
||||
HTTPS string `json:"https"`
|
||||
TLSCert string `json:"tlsCert"`
|
||||
TLSKey string `json:"tlsKey"`
|
||||
}
|
||||
|
||||
// GRPC is the config for the gRPC API.
|
||||
type GRPC struct {
|
||||
// The port to listen on.
|
||||
Addr string `yaml:"addr"`
|
||||
TLSCert string `yaml:"tlsCert"`
|
||||
TLSKey string `yaml:"tlsKey"`
|
||||
TLSClientCA string `yaml:"tlsClientCA"`
|
||||
Addr string `json:"addr"`
|
||||
TLSCert string `json:"tlsCert"`
|
||||
TLSKey string `json:"tlsKey"`
|
||||
TLSClientCA string `json:"tlsClientCA"`
|
||||
}
|
||||
|
||||
// Storage holds app's storage configuration.
|
||||
type Storage struct {
|
||||
Type string `yaml:"type"`
|
||||
Config StorageConfig `yaml:"config"`
|
||||
}
|
||||
|
||||
// UnmarshalYAML allows Storage to unmarshal its config field dynamically
|
||||
// depending on the type of storage.
|
||||
func (s *Storage) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var storageMeta struct {
|
||||
Type string `yaml:"type"`
|
||||
}
|
||||
if err := unmarshal(&storageMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Type = storageMeta.Type
|
||||
// TODO(ericchiang): replace this with a registration process.
|
||||
var err error
|
||||
switch storageMeta.Type {
|
||||
case "kubernetes":
|
||||
var config struct {
|
||||
Config kubernetes.Config `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
s.Config = &config.Config
|
||||
case "memory":
|
||||
var config struct {
|
||||
Config memory.Config `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
s.Config = &config.Config
|
||||
case "sqlite3":
|
||||
var config struct {
|
||||
Config sql.SQLite3 `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
s.Config = &config.Config
|
||||
case "postgres":
|
||||
var config struct {
|
||||
Config sql.Postgres `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
s.Config = &config.Config
|
||||
default:
|
||||
return fmt.Errorf("unknown storage type %q", storageMeta.Type)
|
||||
}
|
||||
return err
|
||||
Type string `json:"type"`
|
||||
Config StorageConfig `json:"config"`
|
||||
}
|
||||
|
||||
// StorageConfig is a configuration that can create a storage.
|
||||
@@ -148,14 +77,49 @@ type StorageConfig interface {
|
||||
Open() (storage.Storage, error)
|
||||
}
|
||||
|
||||
var storages = map[string]func() StorageConfig{
|
||||
"kubernetes": func() StorageConfig { return new(kubernetes.Config) },
|
||||
"memory": func() StorageConfig { return new(memory.Config) },
|
||||
"sqlite3": func() StorageConfig { return new(sql.SQLite3) },
|
||||
"postgres": func() StorageConfig { return new(sql.Postgres) },
|
||||
}
|
||||
|
||||
// UnmarshalJSON allows Storage to implement the unmarshaler interface to
|
||||
// dynamically determine the type of the storage config.
|
||||
func (s *Storage) UnmarshalJSON(b []byte) error {
|
||||
var store struct {
|
||||
Type string `json:"type"`
|
||||
Config json.RawMessage `json:"config"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &store); err != nil {
|
||||
return fmt.Errorf("parse storage: %v", err)
|
||||
}
|
||||
f, ok := storages[store.Type]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown storage type %q", store.Type)
|
||||
}
|
||||
|
||||
storageConfig := f()
|
||||
if len(store.Config) != 0 {
|
||||
if err := json.Unmarshal([]byte(store.Config), storageConfig); err != nil {
|
||||
return fmt.Errorf("parse storace config: %v", err)
|
||||
}
|
||||
}
|
||||
*s = Storage{
|
||||
Type: store.Type,
|
||||
Config: storageConfig,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connector is a magical type that can unmarshal YAML dynamically. The
|
||||
// Type field determines the connector type, which is then customized for Config.
|
||||
type Connector struct {
|
||||
Type string `yaml:"type"`
|
||||
Name string `yaml:"name"`
|
||||
ID string `yaml:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
|
||||
Config ConnectorConfig `yaml:"config"`
|
||||
Config ConnectorConfig `json:"config"`
|
||||
}
|
||||
|
||||
// ConnectorConfig is a configuration that can open a connector.
|
||||
@@ -163,55 +127,43 @@ type ConnectorConfig interface {
|
||||
Open() (connector.Connector, error)
|
||||
}
|
||||
|
||||
// UnmarshalYAML allows Connector to unmarshal its config field dynamically
|
||||
// depending on the type of connector.
|
||||
func (c *Connector) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var connectorMetadata struct {
|
||||
Type string `yaml:"type"`
|
||||
Name string `yaml:"name"`
|
||||
ID string `yaml:"id"`
|
||||
}
|
||||
if err := unmarshal(&connectorMetadata); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Type = connectorMetadata.Type
|
||||
c.Name = connectorMetadata.Name
|
||||
c.ID = connectorMetadata.ID
|
||||
|
||||
var err error
|
||||
switch c.Type {
|
||||
case "mockCallback":
|
||||
var config struct {
|
||||
Config mock.CallbackConfig `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
c.Config = &config.Config
|
||||
case "mockPassword":
|
||||
var config struct {
|
||||
Config mock.PasswordConfig `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
c.Config = &config.Config
|
||||
case "ldap":
|
||||
var config struct {
|
||||
Config ldap.Config `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
c.Config = &config.Config
|
||||
case "github":
|
||||
var config struct {
|
||||
Config github.Config `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
c.Config = &config.Config
|
||||
case "oidc":
|
||||
var config struct {
|
||||
Config oidc.Config `yaml:"config"`
|
||||
}
|
||||
err = unmarshal(&config)
|
||||
c.Config = &config.Config
|
||||
default:
|
||||
return fmt.Errorf("unknown connector type %q", c.Type)
|
||||
}
|
||||
return err
|
||||
var connectors = map[string]func() ConnectorConfig{
|
||||
"mockCallback": func() ConnectorConfig { return new(mock.CallbackConfig) },
|
||||
"mockPassword": func() ConnectorConfig { return new(mock.PasswordConfig) },
|
||||
"ldap": func() ConnectorConfig { return new(ldap.Config) },
|
||||
"github": func() ConnectorConfig { return new(github.Config) },
|
||||
"oidc": func() ConnectorConfig { return new(oidc.Config) },
|
||||
}
|
||||
|
||||
// UnmarshalJSON allows Connector to implement the unmarshaler interface to
|
||||
// dynamically determine the type of the connector config.
|
||||
func (c *Connector) UnmarshalJSON(b []byte) error {
|
||||
var conn struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
|
||||
Config json.RawMessage `json:"config"`
|
||||
}
|
||||
if err := json.Unmarshal(b, &conn); err != nil {
|
||||
return fmt.Errorf("parse connector: %v", err)
|
||||
}
|
||||
f, ok := connectors[conn.Type]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown connector type %q", conn.Type)
|
||||
}
|
||||
|
||||
connConfig := f()
|
||||
if len(conn.Config) != 0 {
|
||||
if err := json.Unmarshal([]byte(conn.Config), connConfig); err != nil {
|
||||
return fmt.Errorf("parse connector config: %v", err)
|
||||
}
|
||||
}
|
||||
*c = Connector{
|
||||
Type: conn.Type,
|
||||
Name: conn.Type,
|
||||
ID: conn.ID,
|
||||
Config: connConfig,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -11,11 +11,11 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/coreos/dex/api"
|
||||
"github.com/coreos/dex/server"
|
||||
@@ -136,13 +136,7 @@ func serve(cmd *cobra.Command, args []string) error {
|
||||
s = storage.WithStaticClients(s, c.StaticClients)
|
||||
}
|
||||
if len(c.StaticPasswords) > 0 {
|
||||
p := make([]storage.Password, len(c.StaticPasswords))
|
||||
for i, pw := range c.StaticPasswords {
|
||||
if p[i], err = pw.toPassword(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s = storage.WithStaticPasswords(s, p)
|
||||
s = storage.WithStaticPasswords(s, c.StaticPasswords)
|
||||
}
|
||||
|
||||
serverConfig := server.Config{
|
||||
|
Reference in New Issue
Block a user