From 3d08d0b4b0679567dcad6543b322394ab8a5fba7 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Tue, 31 Jan 2023 14:30:36 +0100 Subject: [PATCH] feat: initial skeleton setup for SSO mode --- cmd/wonderwall/main.go | 57 +++++++++++++++++++------- pkg/config/config.go | 46 +++++++++++++++++++++ pkg/handler/handler_sso_proxy.go | 68 +++++++++++++++++++++++++++++++ pkg/handler/handler_sso_server.go | 68 +++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+), 15 deletions(-) create mode 100644 pkg/handler/handler_sso_proxy.go create mode 100644 pkg/handler/handler_sso_server.go diff --git a/cmd/wonderwall/main.go b/cmd/wonderwall/main.go index c3a0316..5d7fbe5 100644 --- a/cmd/wonderwall/main.go +++ b/cmd/wonderwall/main.go @@ -18,6 +18,13 @@ import ( "github.com/nais/wonderwall/pkg/server" ) +func main() { + err := run() + if err != nil { + log.Fatalf("Fatal error: %s", err) + } +} + func run() error { cfg, err := config.Initialize() if err != nil { @@ -29,28 +36,29 @@ func run() error { return err } - openidConfig, err := openidconfig.NewConfig(cfg) - if err != nil { - return err - } + crypt := crypto.NewCrypter(key) ctx, cancel := context.WithCancel(context.Background()) defer cancel() - crypt := crypto.NewCrypter(key) + var h router.Source - cookieOpts := cookie.DefaultOptions() - - jwksProvider, err := provider.NewJwksProvider(ctx, openidConfig) - if err != nil { - return err + if cfg.SSO.Enabled { + switch cfg.SSO.Mode { + case config.SSOModeServer: + h, err = ssoServerHandler() + case config.SSOModeProxy: + h, err = ssoProxyHandler() + default: + return fmt.Errorf("invalid SSO mode: %q", cfg.SSO.Mode) + } + } else { + h, err = standaloneHandler(ctx, cfg, crypt) } - h, err := handler.NewHandler(cfg, cookieOpts, jwksProvider, openidConfig, crypt) if err != nil { return fmt.Errorf("initializing routing handler: %w", err) } - r := router.New(h) go func() { @@ -62,9 +70,28 @@ func run() error { return server.Start(cfg, r) } -func main() { - err := run() +func standaloneHandler(ctx context.Context, cfg *config.Config, crypt crypto.Crypter) (router.Source, error) { + openidConfig, err := openidconfig.NewConfig(cfg) if err != nil { - log.Fatalf("Fatal error: %s", err) + return nil, err } + + jwksProvider, err := provider.NewJwksProvider(ctx, openidConfig) + if err != nil { + return nil, err + } + + cookieOpts := cookie.DefaultOptions() + + return handler.NewHandler(cfg, cookieOpts, jwksProvider, openidConfig, crypt) +} + +func ssoServerHandler() (router.Source, error) { + //TODO implement me + panic("implement me") +} + +func ssoProxyHandler() (router.Source, error) { + //TODO implement me + panic("implement me") } diff --git a/pkg/config/config.go b/pkg/config/config.go index 2c1aafc..1c19826 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "net/url" "time" "github.com/nais/liberator/pkg/conftools" @@ -30,6 +31,7 @@ type Config struct { Redis Redis `json:"redis"` Loginstatus Loginstatus `json:"loginstatus"` + SSO SSO `json:"sso"` } type Loginstatus struct { @@ -47,6 +49,20 @@ type Session struct { Refresh bool `json:"refresh"` } +type SSO struct { + Enabled bool `json:"enabled"` + Domain string `json:"domain"` + Mode SSOMode `json:"mode"` + ServerURL string `json:"server-url"` +} + +type SSOMode string + +const ( + SSOModeServer SSOMode = "server" + SSOModeProxy SSOMode = "proxy" +) + const ( BindAddress = "bind-address" LogFormat = "log-format" @@ -70,6 +86,11 @@ const ( LoginstatusCookieName = "loginstatus.cookie-name" LoginstatusResourceIndicator = "loginstatus.resource-indicator" LoginstatusTokenURL = "loginstatus.token-url" + + SSOEnabled = "sso.enabled" + SSODomain = "sso.domain" + SSOModeFlag = "sso.mode" + SSOServerURL = "sso.server-url" ) func Initialize() (*Config, error) { @@ -98,6 +119,11 @@ func Initialize() (*Config, error) { flag.String(LoginstatusResourceIndicator, "", "The resource indicator that should be included in the authorization request to get an audience-restricted token that Loginstatus accepts. Empty means no resource indicator.") flag.String(LoginstatusTokenURL, "", "The URL to the Loginstatus service that returns an opaque token.") + flag.Bool(SSOEnabled, false, "Enable single sign-on mode; one server acting as the OIDC Relying Party, and N proxies. The proxies delegate most endpoint operations to the server, and only implements a reverse proxy that reads the user's session data from the shared store.") + flag.String(SSODomain, "", "The domain that the session cookies should be set for, usually the second-level domain name (e.g. example.com).") + flag.String(SSOModeFlag, string(SSOModeServer), "The SSO mode for this instance. Must be one of 'server' or 'proxy'.") + flag.String(SSOServerURL, "", "The URL that points to the SSO server instance.") + redisFlags() openIDFlags() @@ -157,5 +183,25 @@ func (c *Config) Validate() error { return fmt.Errorf("%q cannot be enabled without %q", SessionInactivity, SessionRefresh) } + if c.SSO.Enabled { + switch c.SSO.Mode { + case SSOModeProxy: + if len(c.SSO.ServerURL) == 0 { + return fmt.Errorf("%q cannot be empty", SSOServerURL) + } + + _, err := url.ParseRequestURI(c.SSO.ServerURL) + if err != nil { + return fmt.Errorf("%q must be a valid url: %w", SSOServerURL, err) + } + case SSOModeServer: + if len(c.SSO.Domain) == 0 { + return fmt.Errorf("%q cannot be empty", SSODomain) + } + default: + return fmt.Errorf("%q must be one of [%q, %q]", SSOModeFlag, SSOModeServer, SSOModeProxy) + } + } + return nil } diff --git a/pkg/handler/handler_sso_proxy.go b/pkg/handler/handler_sso_proxy.go new file mode 100644 index 0000000..a09092f --- /dev/null +++ b/pkg/handler/handler_sso_proxy.go @@ -0,0 +1,68 @@ +package handler + +import ( + "net/http" + + "github.com/nais/wonderwall/pkg/ingress" + "github.com/nais/wonderwall/pkg/router" +) + +var _ router.Source = &SSOProxyHandler{} + +type SSOProxyHandler struct { +} + +func (s *SSOProxyHandler) Login(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) LoginCallback(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) Logout(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) LogoutCallback(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) LogoutFrontChannel(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) LogoutLocal(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) Session(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) SessionRefresh(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) ReverseProxy(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) GetIngresses() *ingress.Ingresses { + //TODO implement me + panic("implement me") +} + +func (s *SSOProxyHandler) GetProviderName() string { + //TODO implement me + panic("implement me") +} diff --git a/pkg/handler/handler_sso_server.go b/pkg/handler/handler_sso_server.go new file mode 100644 index 0000000..ed41c38 --- /dev/null +++ b/pkg/handler/handler_sso_server.go @@ -0,0 +1,68 @@ +package handler + +import ( + "net/http" + + "github.com/nais/wonderwall/pkg/ingress" + "github.com/nais/wonderwall/pkg/router" +) + +var _ router.Source = &SSOServerHandler{} + +type SSOServerHandler struct { +} + +func (s *SSOServerHandler) Login(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) LoginCallback(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) Logout(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) LogoutCallback(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) LogoutFrontChannel(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) LogoutLocal(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) Session(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) SessionRefresh(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) ReverseProxy(w http.ResponseWriter, r *http.Request) { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) GetIngresses() *ingress.Ingresses { + //TODO implement me + panic("implement me") +} + +func (s *SSOServerHandler) GetProviderName() string { + //TODO implement me + panic("implement me") +}