Skip to content
Closed
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ proto: bin/protoc bin/protoc-gen-go
@cp api/v2/*.proto api/
@./bin/protoc --go_out=plugins=grpc:. --plugin=protoc-gen-go=./bin/protoc-gen-go api/*.proto
@./bin/protoc --go_out=. --plugin=protoc-gen-go=./bin/protoc-gen-go server/internal/*.proto
@./bin/protoc --go_out=plugins=grpc:. --plugin=protoc-gen-go=./bin/protoc-gen-go middleware/grpc/api/*.proto

.PHONY: verify-proto
verify-proto: proto
Expand Down
82 changes: 75 additions & 7 deletions cmd/dex/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type Config struct {
// Write operations, like updating a connector, will fail.
StaticConnectors []Connector `json:"connectors"`

// StaticMiddleware are global middleware specified in the ConfigMap.
StaticMiddleware []Middleware `json:"middleware"`

// 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 `json:"staticClients"`
Expand Down Expand Up @@ -236,6 +239,8 @@ type Connector struct {
ID string `json:"id"`

Config server.ConnectorConfig `json:"config"`

Middleware []Middleware `json:"middleware"`
}

// UnmarshalJSON allows Connector to implement the unmarshaler interface to
Expand All @@ -247,6 +252,8 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
ID string `json:"id"`

Config json.RawMessage `json:"config"`

Middleware []Middleware `json:"middleware"`
}
if err := json.Unmarshal(b, &conn); err != nil {
return fmt.Errorf("parse connector: %v", err)
Expand All @@ -268,10 +275,11 @@ func (c *Connector) UnmarshalJSON(b []byte) error {
}
}
*c = Connector{
Type: conn.Type,
Name: conn.Name,
ID: conn.ID,
Config: connConfig,
Type: conn.Type,
Name: conn.Name,
ID: conn.ID,
Config: connConfig,
Middleware: conn.Middleware,
}
return nil
}
Expand All @@ -283,10 +291,70 @@ func ToStorageConnector(c Connector) (storage.Connector, error) {
return storage.Connector{}, fmt.Errorf("failed to marshal connector config: %v", err)
}

mwares := make([]storage.Middleware, len(c.Middleware))
for n, mware := range c.Middleware {
mwares[n], err = ToStorageMiddleware(mware)
if err != nil {
return storage.Connector{}, fmt.Errorf("failed to marshal connector middleware: %v", err)
}
}

return storage.Connector{
ID: c.ID,
Type: c.Type,
Name: c.Name,
ID: c.ID,
Type: c.Type,
Name: c.Name,
Config: data,
Middleware: mwares,
}, nil
}

// Middleware is another magic type, like Connector, that can unmarshal YAML
// dynamically. The Type field determines the middleware type, which is then
// customized for Config.
type Middleware struct {
Type string `json:"type"`

Config server.MiddlewareConfig `json:"config"`
}

// UnmarshalJSON allows Connector to implement the unmarshaler interface to
// dynamically determine the type of the middleware config.
func (m *Middleware) UnmarshalJSON(b []byte) error {
var mware struct {
Type string `json:"type"`

Config json.RawMessage `json:"config"`
}
if err := json.Unmarshal(b, &mware); err != nil {
return fmt.Errorf("parse middleware: %v", err)
}
f, ok := server.MiddlewaresConfig[mware.Type]
if !ok {
return fmt.Errorf("unknown middleware type %q", mware.Type)
}

mwareConfig := f()
if len(mware.Config) != 0 {
if err := json.Unmarshal(mware.Config, mwareConfig); err != nil {
return fmt.Errorf("parse middleware config: %v", err)
}
}
*m = Middleware{
Type: mware.Type,
Config: mwareConfig,
}
return nil
}

// ToStorageMiddleware converts an object to storage middleware type.
func ToStorageMiddleware(m Middleware) (storage.Middleware, error) {
data, err := json.Marshal(m.Config)
if err != nil {
return storage.Middleware{}, fmt.Errorf("failed to marshal middleware config: %v", err)
}

return storage.Middleware{
Type: m.Type,
Config: data,
}, nil
}
Expand Down
22 changes: 22 additions & 0 deletions cmd/dex/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,28 @@ func runServe(options serveOptions) error {

s = storage.WithStaticConnectors(s, storageConnectors)

storageMiddleware := make([]storage.Middleware, len(c.StaticMiddleware))
for i, m := range c.StaticMiddleware {
if m.Type == "" {
return fmt.Errorf("invalid config: Type field is required for a middleware")
}
if m.Config == nil {
return fmt.Errorf("invalid config: no config field for middleware %d (%s)", i, m.Type)
}
logger.Infof("config middleware: %d (%s)", i, m.Type)

// convert to a storage middleware object
mware, err := ToStorageMiddleware(m)
if err != nil {
return fmt.Errorf("failed to initialize storage middleware: %v", err)
}
storageMiddleware[i] = mware
}

if len(storageMiddleware) > 0 {
s = storage.WithStaticMiddleware(s, storageMiddleware)
}

if len(c.OAuth2.ResponseTypes) > 0 {
logger.Infof("config response types accepted: %s", c.OAuth2.ResponseTypes)
}
Expand Down
2 changes: 2 additions & 0 deletions connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Identity struct {

Groups []string

CustomClaims map[string]interface{}

// ConnectorData holds data used by the connector for subsequent requests after initial
// authentication, such as access tokens for upstream provides.
//
Expand Down
163 changes: 163 additions & 0 deletions middleware/claims/claims.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Package claims implements support for manipulating custom claims.
package claims

import (
"context"
"fmt"
"regexp"
"strings"

"github.com/dexidp/dex/connector"
"github.com/dexidp/dex/middleware"
"github.com/dexidp/dex/pkg/log"
)

// Config holds the configuration parameters for the claims middleware.
// The claims middleware provides the ability to filter and prefix custom claims
// returned by things further up the chain.
//
// An example config:
//
// type: claims
// config:
// actions:
// - discard: "^foo\.example\.com/.*$"
// - rename:
// pattern: "^(.*)\.example\.com/(.*)$"
// as: "example.com/$1/$2"
// - stripPrefix: "foo/"
// - addPrefix: "example.com/"
// inject:
// "example.com/foo": bar
// "example.com/foobar": true
//
// Note that this middleware will *not* affect the standard claims made by
// Dex. It will only manipulate custom claims. We don't really anticipate
// much use of this middleware, because custom claims will more often be
// injected on the basis of external logic, which is more appropriately handled
// via gRPC.
//
type Config struct {
// A list of actions to perform on each claim
Actions []Action `json:"actions,omitempty"`

// Additional claims to inject
Inject map[string]interface{} `json:"inject,omitempty"`
}

// Renames claims matching the regular expression Pattern with As
type RenameAction struct {
Pattern string `json:"pattern"`
As string `json:"as"`
}

// An action
type Action struct {
// Discards claims whose names match a regexp
Discard string `json:"discard,omitempty"`

// Renames claims using a regexp
Rename *RenameAction `json:"rename,omitempty"`

// Remove a prefix string from a claim name
StripPrefix string `json:"stripPrefix,omitempty"`

// Add a prefix string to a claim name
AddPrefix string `json:"addPrefix,omitempty"`
}

// The actual Middleware object uses these instead, so that we can pre-compile
// the regular expressions
type renameAction struct {
Regexp *regexp.Regexp
As string
}

type action struct {
Discard *regexp.Regexp
Rename *renameAction
StripPrefix string
AddPrefix string
}

// Open returns a claims Middleware
func (c *Config) Open(logger log.Logger) (middleware.Middleware, error) {
// Compile the regular expressions
actions := make([]action, len(c.Actions))
for n, a := range c.Actions {
actions[n] = action{
StripPrefix: a.StripPrefix,
AddPrefix: a.AddPrefix,
}

if a.Discard != "" {
re, err := regexp.Compile(a.Discard)
if err != nil {
return nil, fmt.Errorf("claims: unable to compile discard regexp %q: %v", a.Discard, err)
}
actions[n].Discard = re
}

if a.Rename != nil {
re, err := regexp.Compile(a.Rename.Pattern)
if err != nil {
return nil, fmt.Errorf("claims: unable to compile rename regexp %q: %v", a.Rename.Pattern, err)
}
actions[n].Rename = &renameAction{
Regexp: re,
As: a.Rename.As,
}
}
}

return &claimsMiddleware{Config: *c, CompiledActions: actions}, nil
}

type claimsMiddleware struct {
Config

CompiledActions []action
}

// Apply the actions to the claims in the incoming identity
func (c *claimsMiddleware) Process(ctx context.Context, identity connector.Identity) (connector.Identity, error) {
newClaims := map[string]interface{}{}

for claim, value := range identity.CustomClaims {
discard := false

for _, action := range c.CompiledActions {
if action.Discard != nil {
if action.Discard.MatchString(claim) {
discard = true
break
}
}

if action.Rename != nil {
claim = action.Rename.Regexp.ReplaceAllString(claim,
action.Rename.As)
}

if action.StripPrefix != "" {
claim = strings.TrimPrefix(claim, action.StripPrefix)
}

if action.AddPrefix != "" {
claim = action.AddPrefix + claim
}
}

if !discard {
newClaims[claim] = value
}
}

for claim, value := range c.Inject {
newClaims[claim] = value
}

identity.CustomClaims = newClaims

return identity, nil
}
Loading