Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: external connectors #2361

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ARG GOPROXY

COPY go.mod go.sum ./
COPY api/v2/go.mod api/v2/go.sum ./api/v2/
COPY connector/external/sdk/go.mod connector/external/sdk/go.sum ./connector/external/sdk/
RUN go mod download

COPY . .
Expand Down Expand Up @@ -52,6 +53,7 @@ RUN chown -R 1001:1001 /etc/dex
# Copy module files for CVE scanning / dependency analysis.
COPY --from=builder /usr/local/src/dex/go.mod /usr/local/src/dex/go.sum /usr/local/src/dex/
COPY --from=builder /usr/local/src/dex/api/v2/go.mod /usr/local/src/dex/api/v2/go.sum /usr/local/src/dex/api/v2/
COPY --from=builder /usr/local/src/dex/connector/external/sdk/go.mod /usr/local/src/dex/connector/external/sdk//go.sum /usr/local/src/dex/connector/external/sdk/

COPY --from=builder /go/bin/dex /usr/local/bin/dex
COPY --from=builder /usr/local/src/dex/web /srv/dex/web
Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ bin/dex:
@mkdir -p bin/
@go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex

examples: bin/grpc-client bin/example-app
examples: bin/grpc-client bin/example-app bin/external-connector bin/external-gitlab

bin/grpc-client:
@mkdir -p bin/
Expand All @@ -45,6 +45,14 @@ bin/example-app:
@mkdir -p bin/
@cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/example-app

bin/external-connector:
@mkdir -p bin/
@cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/external-connector

bin/external-gitlab:
@mkdir -p bin/
@cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/external-gitlab

.PHONY: release-binary
release-binary: generate
@go build -o /go/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
Expand Down Expand Up @@ -108,6 +116,7 @@ FORCE:
proto:
@protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/v2/*.proto
@protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/*.proto
@protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. connector/external/sdk/*.proto
#@cp api/v2/*.proto api/

.PHONY: proto-internal
Expand Down
87 changes: 87 additions & 0 deletions connector/external/callback.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package external

import (
"context"
"fmt"
"io"
"net/http"

"github.com/dexidp/dex/connector"
"github.com/dexidp/dex/connector/external/sdk"
"github.com/dexidp/dex/pkg/log"
)

type CallbackConnectorConfig struct {
gRPCConnectorConfig

id string
logger log.Logger

client sdk.CallbackConnectorClient
}

func (c *CallbackConnectorConfig) Open(id string, logger log.Logger) (connector.Connector, error) {
conn, err := grpcConn(c.Port, c.CAPath, c.ClientCrt, c.ClientKey)
if err != nil {
return connector.Identity{}, err
}

c.id = id
c.client = sdk.NewCallbackConnectorClient(conn)
c.logger = logger
return c, nil
}

func (c *CallbackConnectorConfig) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
response, err := c.client.LoginURL(context.Background(), &sdk.LoginURLReq{
Scopes: toSDKScopes(scopes), CallbackUrl: callbackURL, State: state,
})
if err != nil {
return "", err
}

return response.Url, nil
}

func (c *CallbackConnectorConfig) HandleCallback(scopes connector.Scopes, r *http.Request) (connector.Identity, error) {
body, err := io.ReadAll(r.Body)
if err != nil {
return connector.Identity{}, fmt.Errorf("external connector %q: read body: %v", c.id, err)
}

defer r.Body.Close()

headers := map[string][]string(r.Header)

response, err := c.client.HandleCallback(r.Context(), &sdk.CallbackReq{
Scopes: toSDKScopes(scopes),
Body: body,
Headers: convertToListOfStrings(headers),
RawQuery: r.URL.RawQuery,
})
if err != nil {
return connector.Identity{}, err
}

return toConnectorIdentity(response.Identity), nil
}

func (c *CallbackConnectorConfig) Refresh(ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) {
response, err := c.client.Refresh(ctx, &sdk.RefreshReq{
Scopes: toSDKScopes(scopes), Identity: toSDKIdentity(identity),
})
if err != nil {
return connector.Identity{}, err
}

return toConnectorIdentity(response.GetIdentity()), nil
}

func convertToListOfStrings(oldMap map[string][]string) map[string]*sdk.ListOfStrings {
newMap := make(map[string]*sdk.ListOfStrings, len(oldMap))
for k, v := range oldMap {
newMap[k] = &sdk.ListOfStrings{Value: v}
}

return newMap
}
37 changes: 37 additions & 0 deletions connector/external/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package external

import (
"github.com/dexidp/dex/connector"
"github.com/dexidp/dex/connector/external/sdk"
)

func toSDKScopes(s connector.Scopes) *sdk.Scopes {
return &sdk.Scopes{
OfflineAccess: s.OfflineAccess,
Groups: s.Groups,
}
}

func toSDKIdentity(id connector.Identity) *sdk.Identity {
return &sdk.Identity{
UserId: id.UserID,
Username: id.Username,
PreferredUsername: id.PreferredUsername,
Email: id.Email,
EmailVerified: id.EmailVerified,
Groups: id.Groups,
ConnectorData: id.ConnectorData,
}
}

func toConnectorIdentity(id *sdk.Identity) connector.Identity {
return connector.Identity{
UserID: id.UserId,
Username: id.Username,
PreferredUsername: id.PreferredUsername,
Email: id.Email,
EmailVerified: id.EmailVerified,
Groups: id.Groups,
ConnectorData: id.ConnectorData,
}
}
50 changes: 50 additions & 0 deletions connector/external/grpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package external

import (
"crypto/tls"
"crypto/x509"
"fmt"
"os"

"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

type gRPCConnectorConfig struct {
Port int `json:"port"`
CAPath string `json:"tlsClientCA"`
ClientCrt string `json:"tlsCert"`
ClientKey string `json:"tlsKey"`
}

func grpcConn(port int, ca, crt, key string) (*grpc.ClientConn, error) {
cPool := x509.NewCertPool()
caCert, err := os.ReadFile(ca)
if err != nil {
return nil, fmt.Errorf("invalid CA crt file: %s", ca)
}

if !cPool.AppendCertsFromPEM(caCert) {
return nil, fmt.Errorf("failed to parse CA crt")
}

clientCert, err := tls.LoadX509KeyPair(crt, key)
if err != nil {
return nil, fmt.Errorf("invalid client crt file: %s", crt)
}

clientTLSConfig := &tls.Config{
RootCAs: cPool,
Certificates: []tls.Certificate{clientCert},
}
creds := credentials.NewTLS(clientTLSConfig)

hostAndPort := fmt.Sprintf("127.0.0.1:%d", port)

conn, err := grpc.Dial(hostAndPort, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, fmt.Errorf("dial: %v", err)
}

return conn, nil
}
68 changes: 68 additions & 0 deletions connector/external/password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package external

import (
"context"

"github.com/dexidp/dex/connector"
"github.com/dexidp/dex/connector/external/sdk"
"github.com/dexidp/dex/pkg/log"
)

type PasswordConnectorConfig struct {
gRPCConnectorConfig

id string
logger log.Logger

client sdk.PasswordConnectorClient
}

func (c *PasswordConnectorConfig) Open(id string, logger log.Logger) (connector.Connector, error) {
conn, err := grpcConn(c.Port, c.CAPath, c.ClientCrt, c.ClientKey)
if err != nil {
return connector.Identity{}, err
}

c.id = id
c.client = sdk.NewPasswordConnectorClient(conn)
c.logger = logger
return c, nil
}

func (c *PasswordConnectorConfig) Prompt() string {
prompt := "username"

response, err := c.client.Prompt(context.Background(), &sdk.PromptReq{})
if err != nil {
c.logger.Errorf(err.Error())
return prompt
}

if response.Prompt != "" {
prompt = response.Prompt
}

return prompt
}

func (c *PasswordConnectorConfig) Login(ctx context.Context, scopes connector.Scopes, username, password string) (identity connector.Identity, validPassword bool, err error) {
response, err := c.client.Login(ctx, &sdk.LoginReq{
Scopes: toSDKScopes(scopes), Username: username, Password: password,
})
if err != nil {
return connector.Identity{}, false, err
}

return toConnectorIdentity(response.GetIdentity()), response.GetValidPassword(), nil
}

func (c *PasswordConnectorConfig) Refresh(ctx context.Context, scopes connector.Scopes, identity connector.Identity) (connector.Identity, error) {
response, err := c.client.Refresh(ctx, &sdk.RefreshReq{
Scopes: toSDKScopes(scopes), Identity: toSDKIdentity(identity),
})
if err != nil {
return connector.Identity{}, err
}

return toConnectorIdentity(response.GetIdentity()), nil
}
16 changes: 16 additions & 0 deletions connector/external/sdk/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module github.com/dexidp/dex/connector/external/sdk

go 1.17

require (
google.golang.org/grpc v1.43.0
google.golang.org/protobuf v1.27.1
)

require (
github.com/golang/protobuf v1.5.0 // indirect
golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
golang.org/x/text v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
)
Loading