forked from mystiq/dex
*: add the ability to define passwords statically
This commit is contained in:
parent
cdf0b91690
commit
2909929b17
5 changed files with 107 additions and 9 deletions
3
TODO.md
3
TODO.md
|
@ -33,7 +33,7 @@ Documentation
|
||||||
|
|
||||||
Storage
|
Storage
|
||||||
|
|
||||||
- [ ] Add SQL storage implementation
|
- [x] Add SQL storage implementation
|
||||||
- [ ] Utilize fixes for third party resources in Kubernetes 1.4
|
- [ ] Utilize fixes for third party resources in Kubernetes 1.4
|
||||||
|
|
||||||
UX
|
UX
|
||||||
|
@ -48,3 +48,4 @@ Backend
|
||||||
|
|
||||||
- [ ] Improve logging, possibly switch to logrus
|
- [ ] Improve logging, possibly switch to logrus
|
||||||
- [ ] Standardize OAuth2 error handling
|
- [ ] Standardize OAuth2 error handling
|
||||||
|
- [ ] Switch to github.com/ghodss/yaml for []byte to base64 string logic
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/coreos/dex/connector"
|
"github.com/coreos/dex/connector"
|
||||||
|
@ -26,7 +27,46 @@ type Config struct {
|
||||||
|
|
||||||
Templates server.TemplateConfig `yaml:"templates"`
|
Templates server.TemplateConfig `yaml:"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 `yaml:"staticClients"`
|
||||||
|
|
||||||
|
// If enabled, the server will maintain a list of passwords which can be used
|
||||||
|
// to identify a user.
|
||||||
|
EnablePasswordDB bool `yaml:"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
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth2 describes enabled OAuth2 extensions.
|
// OAuth2 describes enabled OAuth2 extensions.
|
||||||
|
|
|
@ -55,7 +55,8 @@ func serve(cmd *cobra.Command, args []string) error {
|
||||||
errMsg string
|
errMsg string
|
||||||
}{
|
}{
|
||||||
{c.Issuer == "", "no issuer specified in config file"},
|
{c.Issuer == "", "no issuer specified in config file"},
|
||||||
{len(c.Connectors) == 0, "no connectors supplied in config file"},
|
{len(c.Connectors) == 0 && !c.EnablePasswordDB, "no connectors supplied in config file"},
|
||||||
|
{!c.EnablePasswordDB && len(c.StaticPasswords) != 0, "cannot specify static passwords without enabling password db"},
|
||||||
{c.Storage.Config == nil, "no storage suppied in config file"},
|
{c.Storage.Config == nil, "no storage suppied in config file"},
|
||||||
{c.Web.HTTP == "" && c.Web.HTTPS == "", "must supply a HTTP/HTTPS address to listen on"},
|
{c.Web.HTTP == "" && c.Web.HTTPS == "", "must supply a HTTP/HTTPS address to listen on"},
|
||||||
{c.Web.HTTPS != "" && c.Web.TLSCert == "", "no cert specified for HTTPS"},
|
{c.Web.HTTPS != "" && c.Web.TLSCert == "", "no cert specified for HTTPS"},
|
||||||
|
@ -103,6 +104,15 @@ func serve(cmd *cobra.Command, args []string) error {
|
||||||
if len(c.StaticClients) > 0 {
|
if len(c.StaticClients) > 0 {
|
||||||
s = storage.WithStaticClients(s, c.StaticClients)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
serverConfig := server.Config{
|
serverConfig := server.Config{
|
||||||
SupportedResponseTypes: c.OAuth2.ResponseTypes,
|
SupportedResponseTypes: c.OAuth2.ResponseTypes,
|
||||||
|
@ -110,6 +120,7 @@ func serve(cmd *cobra.Command, args []string) error {
|
||||||
Connectors: connectors,
|
Connectors: connectors,
|
||||||
Storage: s,
|
Storage: s,
|
||||||
TemplateConfig: c.Templates,
|
TemplateConfig: c.Templates,
|
||||||
|
EnablePasswordDB: c.EnablePasswordDB,
|
||||||
}
|
}
|
||||||
|
|
||||||
serv, err := server.NewServer(serverConfig)
|
serv, err := server.NewServer(serverConfig)
|
||||||
|
|
|
@ -11,16 +11,23 @@ connectors:
|
||||||
- type: mockCallback
|
- type: mockCallback
|
||||||
id: mock-callback
|
id: mock-callback
|
||||||
name: Mock
|
name: Mock
|
||||||
- type: mockPassword
|
|
||||||
id: mock-password
|
|
||||||
name: Password
|
|
||||||
config:
|
|
||||||
username: "admin"
|
|
||||||
password: "PASSWORD"
|
|
||||||
|
|
||||||
|
# Instead of reading from an external storage, use this list of clients.
|
||||||
staticClients:
|
staticClients:
|
||||||
- id: example-app
|
- id: example-app
|
||||||
redirectURIs:
|
redirectURIs:
|
||||||
- 'http://127.0.0.1:5555/callback'
|
- 'http://127.0.0.1:5555/callback'
|
||||||
name: 'Example App'
|
name: 'Example App'
|
||||||
secret: ZXhhbXBsZS1hcHAtc2VjcmV0
|
secret: ZXhhbXBsZS1hcHAtc2VjcmV0
|
||||||
|
|
||||||
|
# Let dex keep a list of passwords which can be used to login the user.
|
||||||
|
enablePasswordDB: true
|
||||||
|
|
||||||
|
# A static list of passwords to login the end user. By identifying here, dex
|
||||||
|
# won't look in its undlying storage for passwords.
|
||||||
|
staticPasswords:
|
||||||
|
- email: "admin@example.com"
|
||||||
|
# bcrypt hash of the string "password"
|
||||||
|
hash: "JDJhJDE0JDh4TnlVZ3pzSmVuQm4ySlRPT2QvbmVGcUlnQzF4TEFVRFA3VlpTVzhDNWlkLnFPcmNlYUJX"
|
||||||
|
username: "admin"
|
||||||
|
userID: "08a8684b-db88-4b73-90a9-3cd1661f5466"
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
// Tests for this code are in the "memory" package, since this package doesn't
|
// Tests for this code are in the "memory" package, since this package doesn't
|
||||||
// define a concrete storage implementation.
|
// define a concrete storage implementation.
|
||||||
|
@ -53,3 +56,39 @@ func (s staticClientsStorage) DeleteClient(id string) error {
|
||||||
func (s staticClientsStorage) UpdateClient(id string, updater func(old Client) (Client, error)) error {
|
func (s staticClientsStorage) UpdateClient(id string, updater func(old Client) (Client, error)) error {
|
||||||
return errors.New("static clients: read-only cannot update client")
|
return errors.New("static clients: read-only cannot update client")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type staticPasswordsStorage struct {
|
||||||
|
Storage
|
||||||
|
|
||||||
|
passwordsByEmail map[string]Password
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStaticPasswords returns a storage with a read-only set of passwords. Write actions,
|
||||||
|
// such as creating other passwords, will fail.
|
||||||
|
func WithStaticPasswords(s Storage, staticPasswords []Password) Storage {
|
||||||
|
passwordsByEmail := make(map[string]Password, len(staticPasswords))
|
||||||
|
for _, p := range staticPasswords {
|
||||||
|
p.Email = strings.ToLower(p.Email)
|
||||||
|
passwordsByEmail[p.Email] = p
|
||||||
|
}
|
||||||
|
return staticPasswordsStorage{s, passwordsByEmail}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s staticPasswordsStorage) GetPassword(email string) (Password, error) {
|
||||||
|
if password, ok := s.passwordsByEmail[strings.ToLower(email)]; ok {
|
||||||
|
return password, nil
|
||||||
|
}
|
||||||
|
return Password{}, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s staticPasswordsStorage) CreatePassword(p Password) error {
|
||||||
|
return errors.New("static passwords: read-only cannot create password")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s staticPasswordsStorage) DeletePassword(id string) error {
|
||||||
|
return errors.New("static passwords: read-only cannot create password")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s staticPasswordsStorage) UpdatePassword(id string, updater func(old Password) (Password, error)) error {
|
||||||
|
return errors.New("static passwords: read-only cannot update password")
|
||||||
|
}
|
Loading…
Reference in a new issue