Replace Fosite OIDC provider with embedded Dex
All checks were successful
Build and Push Docker Image / build_and_push (push) Successful in 2m26s
All checks were successful
Build and Push Docker Image / build_and_push (push) Successful in 2m26s
This commit is contained in:
@@ -1,24 +1,33 @@
|
||||
package op
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gitlab.com/mobicoop/solidarity/services/mobility-accounts/handlers"
|
||||
"gitlab.com/mobicoop/solidarity/services/mobility-accounts/storage"
|
||||
"github.com/dexidp/dex/server"
|
||||
dexstorage "github.com/dexidp/dex/storage"
|
||||
"github.com/dexidp/dex/storage/memory"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/ory/fosite"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/viper"
|
||||
"gitlab.com/mobicoop/solidarity/services/mobility-accounts/handlers"
|
||||
maconnector "gitlab.com/mobicoop/solidarity/services/mobility-accounts/oidc-provider/connector"
|
||||
"gitlab.com/mobicoop/solidarity/services/mobility-accounts/storage"
|
||||
)
|
||||
|
||||
// OIDCConfig holds the full OIDC provider configuration, decoded from viper.
|
||||
type OIDCConfig struct {
|
||||
Enable bool
|
||||
CSRFKey bool `mapstructure:"csrf_key"`
|
||||
Port bool
|
||||
Port string
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
Namespaces map[string]OIDCNamespaceConfig
|
||||
}
|
||||
|
||||
// OIDCNamespaceConfig holds per-namespace OIDC settings.
|
||||
type OIDCNamespaceConfig struct {
|
||||
Namespace string
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
@@ -27,6 +36,7 @@ type OIDCNamespaceConfig struct {
|
||||
Clients []OIDCClient
|
||||
}
|
||||
|
||||
// OIDCClient represents a static OIDC client.
|
||||
type OIDCClient struct {
|
||||
ID string
|
||||
OIDC bool
|
||||
@@ -41,60 +51,139 @@ type OIDCClient struct {
|
||||
TokenEndpointAuthMethod string `mapstructure:"token_endpoint_auth_method"`
|
||||
}
|
||||
|
||||
type OIDCHandler struct {
|
||||
NamespaceProviders map[string]fosite.OAuth2Provider
|
||||
config OIDCConfig
|
||||
handler handlers.MobilityAccountsHandler
|
||||
Protocol string //HTTP (dev env) or HTTPS
|
||||
PrivateKey *rsa.PrivateKey
|
||||
// NewDexServer creates an http.ServeMux that hosts one Dex OIDC server per namespace.
|
||||
// Each namespace is mounted at /{namespaceName}/ and has its own storage, clients,
|
||||
// and connector instance.
|
||||
func NewDexServer(handler *handlers.MobilityAccountsHandler, stor storage.Storage, cfg *viper.Viper) (*http.ServeMux, error) {
|
||||
var oidcConfig OIDCConfig
|
||||
mapstructure.Decode(cfg.Get("services.oidc_provider").(map[string]any), &oidcConfig)
|
||||
|
||||
baseURL := oidcConfig.BaseURL
|
||||
if baseURL == "" {
|
||||
protocol := "https"
|
||||
if cfg.GetBool("dev_env") {
|
||||
protocol = "http"
|
||||
}
|
||||
baseURL = fmt.Sprintf("%s://0.0.0.0:%s", protocol, cfg.GetString("services.oidc_provider.port"))
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))
|
||||
|
||||
// Register our custom connector type in Dex's global registry
|
||||
server.ConnectorsConfig["mobilityaccounts"] = func() server.ConnectorConfig {
|
||||
return &maconnector.Config{
|
||||
Handler: handler,
|
||||
Storage: stor,
|
||||
Namespace: "", // will be set per-namespace below
|
||||
}
|
||||
}
|
||||
|
||||
for nsName, nsCfg := range oidcConfig.Namespaces {
|
||||
dexServer, err := createNamespaceDexServer(handler, stor, nsCfg, nsName, baseURL, logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create Dex server for namespace %q: %w", nsName, err)
|
||||
}
|
||||
|
||||
prefix := "/" + nsName
|
||||
mux.Handle(prefix+"/", dexServer)
|
||||
}
|
||||
|
||||
return mux, nil
|
||||
}
|
||||
|
||||
func NewOIDCHandler(h handlers.MobilityAccountsHandler, storage storage.Storage, config *viper.Viper) *OIDCHandler {
|
||||
var oidc_config OIDCConfig
|
||||
// createNamespaceDexServer builds a single Dex server.Server for one namespace.
|
||||
func createNamespaceDexServer(handler *handlers.MobilityAccountsHandler, stor storage.Storage, nsCfg OIDCNamespaceConfig, nsName, baseURL string, logger *slog.Logger) (*server.Server, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
mapstructure.Decode(config.Get("services.oidc_provider").(map[string]any), &oidc_config)
|
||||
// In-memory Dex storage
|
||||
dexStore := memory.New(logger)
|
||||
|
||||
providers := map[string]fosite.OAuth2Provider{}
|
||||
// Register static clients
|
||||
clients := buildDexClients(nsCfg.Clients)
|
||||
dexStore = dexstorage.WithStaticClients(dexStore, clients)
|
||||
|
||||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
// Register the connector — we override ConnectorsConfig factory for this namespace
|
||||
// so that Open() will get the right handler/storage/namespace.
|
||||
server.ConnectorsConfig["mobilityaccounts"] = func() server.ConnectorConfig {
|
||||
return &maconnector.Config{
|
||||
Handler: handler,
|
||||
Storage: stor,
|
||||
Namespace: nsCfg.Namespace,
|
||||
}
|
||||
}
|
||||
|
||||
conn := dexstorage.Connector{
|
||||
ID: "mobilityaccounts",
|
||||
Type: "mobilityaccounts",
|
||||
Name: "Mobility Accounts",
|
||||
}
|
||||
dexStore = dexstorage.WithStaticConnectors(dexStore, []dexstorage.Connector{conn})
|
||||
|
||||
issuer := fmt.Sprintf("%s/%s", baseURL, nsName)
|
||||
|
||||
// Determine web config
|
||||
webCfg := server.WebConfig{
|
||||
Issuer: nsName,
|
||||
}
|
||||
if nsCfg.TemplatesDir != "" {
|
||||
webCfg.Dir = nsCfg.TemplatesDir
|
||||
}
|
||||
|
||||
// Dex v2.42 manages signing keys internally via storage.
|
||||
dexServer, err := server.NewServer(ctx, server.Config{
|
||||
Issuer: issuer,
|
||||
Storage: dexStore,
|
||||
SkipApprovalScreen: true,
|
||||
IDTokensValidFor: 30 * time.Hour,
|
||||
Web: webCfg,
|
||||
Logger: logger,
|
||||
Now: time.Now,
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil, fmt.Errorf("failed to create Dex server: %w", err)
|
||||
}
|
||||
|
||||
for _, c := range oidc_config.Namespaces {
|
||||
np := NewProvider(c, h, storage, privateKey)
|
||||
|
||||
providers[c.Namespace] = np
|
||||
}
|
||||
|
||||
protocol := "https"
|
||||
if config.GetBool("dev_env") {
|
||||
protocol = "http"
|
||||
}
|
||||
|
||||
return &OIDCHandler{
|
||||
config: oidc_config,
|
||||
handler: h,
|
||||
NamespaceProviders: providers,
|
||||
Protocol: protocol,
|
||||
PrivateKey: privateKey,
|
||||
}
|
||||
return dexServer, nil
|
||||
}
|
||||
|
||||
func Run(done chan error, cfg *viper.Viper, handler handlers.MobilityAccountsHandler, storage storage.Storage) {
|
||||
var (
|
||||
address = "0.0.0.0:" + cfg.GetString("services.oidc_provider.port")
|
||||
)
|
||||
// buildDexClients converts the config OIDCClient list to Dex storage.Client list.
|
||||
func buildDexClients(clients []OIDCClient) []dexstorage.Client {
|
||||
dexClients := make([]dexstorage.Client, 0, len(clients))
|
||||
for _, c := range clients {
|
||||
dexClients = append(dexClients, dexstorage.Client{
|
||||
ID: c.ID,
|
||||
Secret: c.Secret,
|
||||
RedirectURIs: c.RedirectURIs,
|
||||
Public: c.Public,
|
||||
Name: c.ID,
|
||||
})
|
||||
}
|
||||
return dexClients
|
||||
}
|
||||
|
||||
log.Info().Str("address", address).Msg("Running OIDC provider")
|
||||
|
||||
s := NewOIDCHandler(handler, storage, cfg)
|
||||
|
||||
err := NewOIDCServer(s, cfg)
|
||||
// Run starts the OIDC provider HTTP server. Called from main.go as a goroutine.
|
||||
func Run(done chan error, cfg *viper.Viper, handler handlers.MobilityAccountsHandler, stor storage.Storage) {
|
||||
address := "0.0.0.0:" + cfg.GetString("services.oidc_provider.port")
|
||||
log.Info().Str("address", address).Msg("Running Dex OIDC provider")
|
||||
|
||||
mux, err := NewDexServer(&handler, stor, cfg)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("OIDC server ended")
|
||||
log.Error().Err(err).Msg("Failed to create Dex OIDC server")
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: mux,
|
||||
Addr: address,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
err = srv.ListenAndServe()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Dex OIDC server ended")
|
||||
}
|
||||
done <- err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user