From 32f6399ccdd35339fcc919b2782fc56c3062a98e Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 14:18:55 -0400 Subject: [PATCH 1/9] new generic template; replace some bad gateways and not founds (#1012) --- endpoints/proxy/frontend.go | 21 +- endpoints/proxyUi/embed.go | 2 +- endpoints/proxyUi/notFound.go | 22 -- endpoints/proxyUi/notFound.html | 400 -------------------------------- endpoints/proxyUi/template.go | 82 +++++++ endpoints/proxyUi/template.html | 90 +++++++ endpoints/publicProxy/config.go | 1 + endpoints/publicProxy/http.go | 15 +- 8 files changed, 199 insertions(+), 434 deletions(-) delete mode 100644 endpoints/proxyUi/notFound.go delete mode 100644 endpoints/proxyUi/notFound.html create mode 100644 endpoints/proxyUi/template.go create mode 100644 endpoints/proxyUi/template.html diff --git a/endpoints/proxy/frontend.go b/endpoints/proxy/frontend.go index 557d38cf..15283f33 100644 --- a/endpoints/proxy/frontend.go +++ b/endpoints/proxy/frontend.go @@ -25,6 +25,7 @@ type FrontendConfig struct { ShrToken string Address string ResponseHeaders []string + TemplatePath string Tls *endpoints.TlsConfig RequestsChan chan *endpoints.Request SuperNetwork bool @@ -131,7 +132,13 @@ func newServiceProxy(cfg *FrontendConfig, ctx ziti.Context) (*httputil.ReversePr } proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { logrus.Errorf("error proxying: %v", err) - proxyUi.WriteNotFound(w) + proxyUi.WriteBadGateway( + w, proxyUi.TemplateData( + "bad gateway!", + fmt.Sprintf("bad gateway for share %v!", cfg.ShrToken), + ), + cfg.TemplatePath, + ) } return proxy, nil } @@ -172,8 +179,8 @@ func serviceTargetProxy(cfg *FrontendConfig, ctx ziti.Context) *httputil.Reverse func authHandler(shrToken string, handler http.Handler, realm string, cfg *FrontendConfig, ctx ziti.Context) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if svc, found := endpoints.GetRefreshedService(shrToken, ctx); found { - if cfg, found := svc.Config[sdk.ZrokProxyConfig]; found { - if scheme, found := cfg["auth_scheme"]; found { + if proxyCfg, found := svc.Config[sdk.ZrokProxyConfig]; found { + if scheme, found := proxyCfg["auth_scheme"]; found { switch scheme { case string(sdk.None): logrus.Debugf("auth scheme none '%v'", shrToken) @@ -188,7 +195,7 @@ func authHandler(shrToken string, handler http.Handler, realm string, cfg *Front return } authed := false - if v, found := cfg["basic_auth"]; found { + if v, found := proxyCfg["basic_auth"]; found { if basicAuth, ok := v.(map[string]interface{}); ok { if v, found := basicAuth["users"]; found { if arr, ok := v.([]interface{}); ok { @@ -231,15 +238,15 @@ func authHandler(shrToken string, handler http.Handler, realm string, cfg *Front } } else { logrus.Warnf("%v -> no auth scheme for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) } } else { logrus.Warnf("%v -> no proxy config for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) } } else { logrus.Warnf("%v -> service '%v' not found", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) } } } diff --git a/endpoints/proxyUi/embed.go b/endpoints/proxyUi/embed.go index 92d18c7a..f31b4cc1 100644 --- a/endpoints/proxyUi/embed.go +++ b/endpoints/proxyUi/embed.go @@ -2,5 +2,5 @@ package proxyUi import "embed" -//go:embed health.html intersititial.html notFound.html unauthorized.html +//go:embed health.html intersititial.html unauthorized.html template.html var FS embed.FS diff --git a/endpoints/proxyUi/notFound.go b/endpoints/proxyUi/notFound.go deleted file mode 100644 index 44cd3365..00000000 --- a/endpoints/proxyUi/notFound.go +++ /dev/null @@ -1,22 +0,0 @@ -package proxyUi - -import ( - "net/http" - - "github.com/sirupsen/logrus" -) - -func WriteNotFound(w http.ResponseWriter) { - if data, err := FS.ReadFile("notFound.html"); err == nil { - w.WriteHeader(http.StatusNotFound) - n, err := w.Write(data) - if n != len(data) { - logrus.Errorf("short write") - return - } - if err != nil { - logrus.Error(err) - return - } - } -} diff --git a/endpoints/proxyUi/notFound.html b/endpoints/proxyUi/notFound.html deleted file mode 100644 index b13dceab..00000000 --- a/endpoints/proxyUi/notFound.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - zrok - - - -
- -
-
-

not found!

-
-
-
- - diff --git a/endpoints/proxyUi/template.go b/endpoints/proxyUi/template.go new file mode 100644 index 00000000..bda1b17d --- /dev/null +++ b/endpoints/proxyUi/template.go @@ -0,0 +1,82 @@ +package proxyUi + +import ( + "bytes" + "fmt" + "net/http" + "os" + "text/template" + + "github.com/sirupsen/logrus" +) + +var externalTemplate []byte + +func WriteBadGateway(w http.ResponseWriter, variableData map[string]interface{}, templatePath string) { + WriteTemplate(w, http.StatusBadGateway, variableData, templatePath) +} + +func TemplateData(title, banner string) map[string]interface{} { + return map[string]interface{}{ + "title": title, + "banner": banner, + } +} + +func NotFoundData(shareToken string) map[string]interface{} { + return TemplateData( + fmt.Sprintf("'%v' not found!", shareToken), + fmt.Sprintf("share %v not found!", shareToken), + ) +} + +func WriteNotFound(w http.ResponseWriter, variableData map[string]interface{}, templatePath string) { + WriteTemplate(w, http.StatusNotFound, variableData, templatePath) +} + +func WriteTemplate(w http.ResponseWriter, statusCode int, variableData map[string]interface{}, templatePath string) { + if templatePath != "" && externalTemplate == nil { + if f, err := os.ReadFile(templatePath); err == nil { + externalTemplate = f + } else { + logrus.Errorf("error reading proxyUi template from '%v': %v", templatePath, err) + } + } + var templateData = externalTemplate + if templateData == nil { + if f, err := FS.ReadFile("template.html"); err == nil { + templateData = f + } else { + logrus.Errorf("error reading embedded proxyUi template 'template.html': %v", err) + } + } + + tmpl, err := template.New("template").Parse(string(templateData)) + if err != nil { + logrus.Errorf("failed to parse template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + logrus.Warnf("merging variable data: %v", variableData) + var buf bytes.Buffer + if err := tmpl.Execute(&buf, variableData); err != nil { + logrus.Errorf("failed to execute template: %v", err) + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + + // Write the response + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.WriteHeader(statusCode) + + n, err := w.Write(buf.Bytes()) + if n != buf.Len() { + logrus.Errorf("short write: wrote %d bytes, expected %d", n, buf.Len()) + return + } + if err != nil { + logrus.Errorf("failed to write response: %v", err) + return + } +} diff --git a/endpoints/proxyUi/template.html b/endpoints/proxyUi/template.html new file mode 100644 index 00000000..b6dfd652 --- /dev/null +++ b/endpoints/proxyUi/template.html @@ -0,0 +1,90 @@ + + + + + + + + + + + zrok - {{ $.title }} + + + +
+ +
+
+

{{ $.banner }}

+
+
+
+ + \ No newline at end of file diff --git a/endpoints/publicProxy/config.go b/endpoints/publicProxy/config.go index b108f71c..d13f3520 100644 --- a/endpoints/publicProxy/config.go +++ b/endpoints/publicProxy/config.go @@ -21,6 +21,7 @@ type Config struct { Identity string Address string HostMatch string + TemplatePath string Interstitial *InterstitialConfig Oauth *OauthConfig Tls *endpoints.TlsConfig diff --git a/endpoints/publicProxy/http.go b/endpoints/publicProxy/http.go index 1c953ade..f0aeb083 100644 --- a/endpoints/publicProxy/http.go +++ b/endpoints/publicProxy/http.go @@ -105,7 +105,14 @@ func newServiceProxy(cfg *Config, ctx ziti.Context) (*httputil.ReverseProxy, err } proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { logrus.Errorf("error proxying: %v", err) - proxyUi.WriteNotFound(w) + proxyUi.WriteBadGateway( + w, + proxyUi.TemplateData( + "bad gateway!", + "bad gateway!", + ), + cfg.TemplatePath, + ) } return proxy, nil } @@ -157,14 +164,14 @@ func shareHandler(handler http.Handler, cfg *Config, signingKey []byte, ctx ziti svc, found := endpoints.GetRefreshedService(shrToken, ctx) if !found { logrus.Warnf("%v -> service '%v' not found", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) return } svcCfg, found := svc.Config[sdk.ZrokProxyConfig] if !found { logrus.Warnf("%v -> no proxy config for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) return } @@ -175,7 +182,7 @@ func shareHandler(handler http.Handler, cfg *Config, signingKey []byte, ctx ziti authScheme, found := svcCfg["auth_scheme"] if !found { logrus.Warnf("%v -> no auth scheme for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) return } From f155ea77e71eebb23ec460cd6e0b49893c5bda24 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 15:24:04 -0400 Subject: [PATCH 2/9] unauthorized (#1012) --- endpoints/proxy/frontend.go | 4 +- endpoints/proxyUi/embed.go | 2 +- endpoints/proxyUi/template.go | 38 ++- endpoints/proxyUi/template.html | 6 + endpoints/proxyUi/unauthorized.go | 22 -- endpoints/proxyUi/unauthorized.html | 400 ------------------------ endpoints/publicProxy/authOAuth.go | 11 +- endpoints/publicProxy/config.go | 8 +- endpoints/publicProxy/cookies.go | 23 +- endpoints/publicProxy/http.go | 7 +- endpoints/publicProxy/providerGithub.go | 76 +++-- endpoints/publicProxy/providerGoogle.go | 53 ++-- endpoints/publicProxy/providerOidc.go | 63 ++-- 13 files changed, 168 insertions(+), 545 deletions(-) delete mode 100644 endpoints/proxyUi/unauthorized.go delete mode 100644 endpoints/proxyUi/unauthorized.html diff --git a/endpoints/proxy/frontend.go b/endpoints/proxy/frontend.go index 15283f33..7992194e 100644 --- a/endpoints/proxy/frontend.go +++ b/endpoints/proxy/frontend.go @@ -133,10 +133,10 @@ func newServiceProxy(cfg *FrontendConfig, ctx ziti.Context) (*httputil.ReversePr proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { logrus.Errorf("error proxying: %v", err) proxyUi.WriteBadGateway( - w, proxyUi.TemplateData( + w, proxyUi.RequiredData( "bad gateway!", fmt.Sprintf("bad gateway for share %v!", cfg.ShrToken), - ), + ).WithError(err), cfg.TemplatePath, ) } diff --git a/endpoints/proxyUi/embed.go b/endpoints/proxyUi/embed.go index f31b4cc1..78f901d3 100644 --- a/endpoints/proxyUi/embed.go +++ b/endpoints/proxyUi/embed.go @@ -2,5 +2,5 @@ package proxyUi import "embed" -//go:embed health.html intersititial.html unauthorized.html template.html +//go:embed health.html intersititial.html template.html var FS embed.FS diff --git a/endpoints/proxyUi/template.go b/endpoints/proxyUi/template.go index bda1b17d..32f0d0c5 100644 --- a/endpoints/proxyUi/template.go +++ b/endpoints/proxyUi/template.go @@ -12,29 +12,54 @@ import ( var externalTemplate []byte -func WriteBadGateway(w http.ResponseWriter, variableData map[string]interface{}, templatePath string) { +type VariableData map[string]interface{} + +func WriteBadGateway(w http.ResponseWriter, variableData VariableData, templatePath string) { WriteTemplate(w, http.StatusBadGateway, variableData, templatePath) } -func TemplateData(title, banner string) map[string]interface{} { +func RequiredData(title, banner string) VariableData { return map[string]interface{}{ "title": title, "banner": banner, } } -func NotFoundData(shareToken string) map[string]interface{} { - return TemplateData( +func (vd VariableData) WithError(err error) VariableData { + vd["error"] = err.Error() + return vd +} + +func NotFoundData(shareToken string) VariableData { + return RequiredData( fmt.Sprintf("'%v' not found!", shareToken), fmt.Sprintf("share %v not found!", shareToken), ) } -func WriteNotFound(w http.ResponseWriter, variableData map[string]interface{}, templatePath string) { +func WriteNotFound(w http.ResponseWriter, variableData VariableData, templatePath string) { WriteTemplate(w, http.StatusNotFound, variableData, templatePath) } -func WriteTemplate(w http.ResponseWriter, statusCode int, variableData map[string]interface{}, templatePath string) { +func UnauthorizedData() VariableData { + return RequiredData( + "unauthorized!", + "user not authorized!", + ) +} + +func UnauthorizedUser(user string) VariableData { + return RequiredData( + "unauthorized!", + fmt.Sprintf("user %v not authorized to access share!", user), + ) +} + +func WriteUnauthorized(w http.ResponseWriter, variableData VariableData, templatePath string) { + WriteTemplate(w, http.StatusUnauthorized, variableData, templatePath) +} + +func WriteTemplate(w http.ResponseWriter, statusCode int, variableData VariableData, templatePath string) { if templatePath != "" && externalTemplate == nil { if f, err := os.ReadFile(templatePath); err == nil { externalTemplate = f @@ -58,7 +83,6 @@ func WriteTemplate(w http.ResponseWriter, statusCode int, variableData map[strin return } - logrus.Warnf("merging variable data: %v", variableData) var buf bytes.Buffer if err := tmpl.Execute(&buf, variableData); err != nil { logrus.Errorf("failed to execute template: %v", err) diff --git a/endpoints/proxyUi/template.html b/endpoints/proxyUi/template.html index b6dfd652..70829561 100644 --- a/endpoints/proxyUi/template.html +++ b/endpoints/proxyUi/template.html @@ -83,6 +83,12 @@

{{ $.banner }}

+ + {{ if $.error }} +
+ Error:{{ $.error }} +
+ {{ end }}
diff --git a/endpoints/proxyUi/unauthorized.go b/endpoints/proxyUi/unauthorized.go deleted file mode 100644 index 08d9c548..00000000 --- a/endpoints/proxyUi/unauthorized.go +++ /dev/null @@ -1,22 +0,0 @@ -package proxyUi - -import ( - "net/http" - - "github.com/sirupsen/logrus" -) - -func WriteUnauthorized(w http.ResponseWriter) { - if data, err := FS.ReadFile("unauthorized.html"); err == nil { - w.WriteHeader(http.StatusUnauthorized) - n, err := w.Write(data) - if n != len(data) { - logrus.Errorf("short write") - return - } - if err != nil { - logrus.Error(err) - return - } - } -} diff --git a/endpoints/proxyUi/unauthorized.html b/endpoints/proxyUi/unauthorized.html deleted file mode 100644 index b2920124..00000000 --- a/endpoints/proxyUi/unauthorized.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - zrok - - - -
- -
-
-

Unauthorized

-
-
-
- - diff --git a/endpoints/publicProxy/authOAuth.go b/endpoints/publicProxy/authOAuth.go index fde57fd9..5925bc36 100644 --- a/endpoints/publicProxy/authOAuth.go +++ b/endpoints/publicProxy/authOAuth.go @@ -58,7 +58,7 @@ func (h *authHandler) handleOAuth(w http.ResponseWriter, r *http.Request, cfg ma return false } - if !h.validateEmailDomain(w, oauthMap, cookie) { + if !h.validateEmailDomain(w, oauthMap, cookie, h.cfg) { return false } @@ -105,7 +105,7 @@ func (h *authHandler) validateOAuthToken(w http.ResponseWriter, r *http.Request, return true } -func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[string]interface{}, cookie *http.Cookie) bool { +func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[string]interface{}, cookie *http.Cookie, cfg *Config) bool { if patterns, found := oauthCfg["email_domains"].([]interface{}); found && len(patterns) > 0 { tkn, _ := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return h.signingKey, nil @@ -116,8 +116,9 @@ func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[st if castedPattern, ok := pattern.(string); ok { match, err := glob.Compile(castedPattern) if err != nil { - logrus.Errorf("invalid email address pattern glob '%v': %v", pattern, err) - proxyUi.WriteUnauthorized(w) + err := fmt.Errorf("invalid email address pattern glob '%v': %v", pattern, err) + logrus.Error(err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(err), cfg.TemplatePath) return false } if match.Match(claims.Email) { @@ -126,7 +127,7 @@ func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[st } } logrus.Warnf("unauthorized email '%v'", claims.Email) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email), cfg.TemplatePath) return false } return true diff --git a/endpoints/publicProxy/config.go b/endpoints/publicProxy/config.go index d13f3520..4a295e88 100644 --- a/endpoints/publicProxy/config.go +++ b/endpoints/publicProxy/config.go @@ -102,7 +102,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { if t, found := mv["type"]; found { switch t { case "github": - cfger, err := newGithubConfigurer(cfg.Oauth, tls, mv) + cfger, err := newGithubConfigurer(cfg, tls, mv) if err != nil { return err } @@ -111,7 +111,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { } case "google": - cfger, err := newGoogleConfigurer(cfg.Oauth, tls, mv) + cfger, err := newGoogleConfigurer(cfg, tls, mv) if err != nil { return err } @@ -120,7 +120,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { } case "oidc": - cfger, err := newOidcConfigurer(cfg.Oauth, tls, mv) + cfger, err := newOidcConfigurer(cfg, tls, mv) if err != nil { return err } @@ -140,7 +140,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData(), cfg.TemplatePath) }) zhttp.StartServer(ctx, cfg.Oauth.BindAddress) diff --git a/endpoints/publicProxy/cookies.go b/endpoints/publicProxy/cookies.go index 76412f9a..6e3ae2d2 100644 --- a/endpoints/publicProxy/cookies.go +++ b/endpoints/publicProxy/cookies.go @@ -7,11 +7,13 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/openziti/zrok/endpoints/proxyUi" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) type sessionCookieRequest struct { - cfg *OauthConfig + cfg *Config + oauthCfg *OauthConfig supportsRefresh bool email string accessToken string @@ -25,8 +27,9 @@ type sessionCookieRequest struct { func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { targetHost := strings.TrimSpace(req.targetHost) if targetHost == "" { - logrus.Error("targetHost claim must not be empty") - proxyUi.WriteUnauthorized(w) + err := errors.New("targetHost claim must not be empty") + logrus.Error(err) + proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err), req.cfg.TemplatePath) return } targetHost = strings.Split(targetHost, "/")[0] @@ -34,7 +37,7 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { encryptedAccessToken, err := encryptToken(req.accessToken, req.encryptionKey) if err != nil { logrus.Errorf("failed to encrypt access token: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(errors.New("failed to encrypt access token")), req.cfg.TemplatePath) return } @@ -47,23 +50,23 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { RefreshInterval: req.refreshInterval, NextRefresh: time.Now().Add(req.refreshInterval), RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(req.cfg.SessionLifetime)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(req.oauthCfg.SessionLifetime)), }, }) sTkn, err := tkn.SignedString(req.signingKey) if err != nil { logrus.Errorf("error signing jwt: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(req.email).WithError(errors.New("error signing jwt")), req.cfg.TemplatePath) return } http.SetCookie(w, &http.Cookie{ - Name: req.cfg.CookieName, + Name: req.oauthCfg.CookieName, Value: sTkn, - MaxAge: int(req.cfg.SessionLifetime.Seconds()), - Domain: req.cfg.CookieDomain, + MaxAge: int(req.oauthCfg.SessionLifetime.Seconds()), + Domain: req.oauthCfg.CookieDomain, Path: "/", - Expires: time.Now().Add(req.cfg.SessionLifetime), + Expires: time.Now().Add(req.oauthCfg.SessionLifetime), // Secure: true, // pending server tls feature https://github.com/openziti/zrok/issues/24 HttpOnly: true, // enabled because zrok frontend is the only intended consumer of this cookie, not client-side scripts SameSite: http.SameSiteLaxMode, // explicitly set to the default Lax mode which allows the zrok share to be navigated to from another site and receive the cookie diff --git a/endpoints/publicProxy/http.go b/endpoints/publicProxy/http.go index f0aeb083..5f4ef908 100644 --- a/endpoints/publicProxy/http.go +++ b/endpoints/publicProxy/http.go @@ -107,7 +107,7 @@ func newServiceProxy(cfg *Config, ctx ziti.Context) (*httputil.ReverseProxy, err logrus.Errorf("error proxying: %v", err) proxyUi.WriteBadGateway( w, - proxyUi.TemplateData( + proxyUi.RequiredData( "bad gateway!", "bad gateway!", ), @@ -206,8 +206,9 @@ func shareHandler(handler http.Handler, cfg *Config, signingKey []byte, ctx ziti } default: - logrus.Infof("invalid auth scheme '%v'", authScheme) - proxyUi.WriteUnauthorized(w) + err := fmt.Errorf("invalid auth scheme '%v'", authScheme) + logrus.Error(err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(err), cfg.TemplatePath) } } } diff --git a/endpoints/publicProxy/providerGithub.go b/endpoints/publicProxy/providerGithub.go index d89a97ed..dd920739 100644 --- a/endpoints/publicProxy/providerGithub.go +++ b/endpoints/publicProxy/providerGithub.go @@ -13,6 +13,7 @@ import ( "github.com/google/uuid" "github.com/mitchellh/mapstructure" "github.com/openziti/zrok/endpoints/proxyUi" + "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/zitadel/oidc/v3/pkg/client/rp" zhttp "github.com/zitadel/oidc/v3/pkg/http" @@ -22,12 +23,12 @@ import ( ) type githubConfigurer struct { - cfg *OauthConfig + cfg *Config githubCfg *githubConfig tls bool } -func newGithubConfigurer(cfg *OauthConfig, tls bool, v map[string]interface{}) (*githubConfigurer, error) { +func newGithubConfigurer(cfg *Config, tls bool, v map[string]interface{}) (*githubConfigurer, error) { c := &githubConfigurer{cfg: cfg} githubCfg, err := newGithubConfig(v) if err != nil { @@ -58,20 +59,20 @@ func (c *githubConfigurer) configure() error { scheme = "https" } - signingKey, err := deriveKey(c.cfg.SigningKey, 32) + signingKey, err := deriveKey(c.cfg.Oauth.SigningKey, 32) if err != nil { return err } - encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32) + encryptionKey, err := deriveKey(c.cfg.Oauth.EncryptionKey, 32) if err != nil { return err } - cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.CookieDomain)) + cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.Oauth.CookieDomain)) rpConfig := &oauth2.Config{ ClientID: c.githubCfg.ClientId, ClientSecret: c.githubCfg.ClientSecret, - RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.EndpointUrl, c.githubCfg.Name), + RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.Oauth.EndpointUrl, c.githubCfg.Name), Scopes: []string{"user:email"}, Endpoint: githubOAuth.Endpoint, } @@ -95,8 +96,9 @@ func (c *githubConfigurer) configure() error { return func(w http.ResponseWriter, r *http.Request) { targetHost, err := url.QueryUnescape(r.URL.Query().Get("targetHost")) if err != nil { - logrus.Errorf("unable to unescape targetHost: %v", err) - proxyUi.WriteUnauthorized(w) + err := fmt.Errorf("unable to unescape targetHost: %v", err) + logrus.Error(err) + proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err), c.cfg.TemplatePath) return } rp.AuthURLHandler(func() string { @@ -106,7 +108,7 @@ func (c *githubConfigurer) configure() error { TargetHost: targetHost, RefreshInterval: r.URL.Query().Get("refreshInterval"), RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.IntermediateLifetime)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.Oauth.IntermediateLifetime)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "zrok", @@ -129,8 +131,9 @@ func (c *githubConfigurer) configure() error { return signingKey, nil }) if err != nil { - logrus.Errorf("error parsing intermediate token: %v", err) - proxyUi.WriteUnauthorized(w) + errOut := errors.Wrap(err, "error parsing intermediate token") + logrus.Error(errOut) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) return } @@ -138,15 +141,17 @@ func (c *githubConfigurer) configure() error { if v, err := time.ParseDuration(token.Claims.(*IntermediateJWT).RefreshInterval); err == nil { refreshInterval = v } else { - logrus.Errorf("unable to parse authorization check interval: %v", err) - proxyUi.WriteUnauthorized(w) + errOut := errors.Wrapf(err, "unable to parse authorization check interval '%v'", token.Claims.(*IntermediateJWT).RefreshInterval) + logrus.Error(errOut) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) return } parsedUrl, err := url.Parse("https://api.github.com/user/emails") if err != nil { - logrus.Errorf("unable to parse api.github.com url: %v", err) - proxyUi.WriteUnauthorized(w) + errOut := errors.Wrap(err, "error parsing github url") + logrus.Error(errOut) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) return } req := &http.Request{ @@ -157,8 +162,9 @@ func (c *githubConfigurer) configure() error { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokens.AccessToken)) resp, err := http.DefaultClient.Do(req) if err != nil { - logrus.Errorf("error getting user info from github: %v", err) - proxyUi.WriteUnauthorized(w) + errOut := errors.Wrap(err, "error getting user info from github") + logrus.Error(errOut) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) return } defer func() { @@ -166,15 +172,17 @@ func (c *githubConfigurer) configure() error { }() response, err := io.ReadAll(resp.Body) if err != nil { - logrus.Errorf("error reading response body: %v", err) - proxyUi.WriteUnauthorized(w) + errOut := errors.Wrap(err, "error reading response body from github") + logrus.Error(errOut) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) return } var rDat []githubUserResp err = json.Unmarshal(response, &rDat) if err != nil { - logrus.Errorf("error unmarshalling github oauth response: %v", err) - proxyUi.WriteUnauthorized(w) + errOut := errors.Wrap(err, "error unmarshalling response from github") + logrus.Error(errOut) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) return } @@ -187,7 +195,7 @@ func (c *githubConfigurer) configure() error { } setSessionCookie(w, sessionCookieRequest{ - cfg: c.cfg, + oauthCfg: c.cfg.Oauth, supportsRefresh: false, email: primaryEmail, accessToken: tokens.AccessToken, @@ -203,7 +211,7 @@ func (c *githubConfigurer) configure() error { http.Handle(fmt.Sprintf("/%v/auth/callback", c.githubCfg.Name), rp.CodeExchangeHandler(login, provider)) logout := func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(c.cfg.CookieName) + cookie, err := r.Cookie(c.cfg.Oauth.CookieName) if err == nil { tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return signingKey, nil @@ -218,7 +226,7 @@ func (c *githubConfigurer) configure() error { strings.NewReader(fmt.Sprintf(`{"access_token":"%s"}`, accessToken))) if err != nil { logrus.Errorf("error creating access token delete request for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("error creating access token delete request")), c.cfg.TemplatePath) return } @@ -228,7 +236,7 @@ func (c *githubConfigurer) configure() error { resp, err := http.DefaultClient.Do(req) if err != nil { logrus.Errorf("error invoking access token delete request for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("error executing access token delete request")), c.cfg.TemplatePath) return } defer resp.Body.Close() @@ -237,42 +245,42 @@ func (c *githubConfigurer) configure() error { logrus.Infof("revoked github access token for '%v'", claims.Email) } else { logrus.Errorf("access token revocation failed with status: %v", resp.StatusCode) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed")), c.cfg.TemplatePath) return } } else { logrus.Errorf("unable to decrypt access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) return } } else { logrus.Errorf("expected provider name '%v' got '%v'", c.githubCfg.Name, claims.Email) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider name mismatch")), c.cfg.TemplatePath) return } } else { logrus.Errorf("invalid jwt; unable to parse: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse")), c.cfg.TemplatePath) return } } else { - logrus.Errorf("error getting cookie '%v': %v", c.cfg.CookieName, err) - proxyUi.WriteUnauthorized(w) + logrus.Errorf("error getting cookie '%v': %v", c.cfg.Oauth.CookieName, err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid cookie")), c.cfg.TemplatePath) return } http.SetCookie(w, &http.Cookie{ - Name: c.cfg.CookieName, + Name: c.cfg.Oauth.CookieName, Value: "", MaxAge: -1, - Domain: c.cfg.CookieDomain, + Domain: c.cfg.Oauth.CookieDomain, Path: "/", HttpOnly: true, }) redirectURL := r.URL.Query().Get("redirect_url") if redirectURL == "" { - redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.EndpointUrl, c.githubCfg.Name) + redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.Oauth.EndpointUrl, c.githubCfg.Name) } http.Redirect(w, r, redirectURL, http.StatusFound) } diff --git a/endpoints/publicProxy/providerGoogle.go b/endpoints/publicProxy/providerGoogle.go index c3ff39e8..c30e205f 100644 --- a/endpoints/publicProxy/providerGoogle.go +++ b/endpoints/publicProxy/providerGoogle.go @@ -2,6 +2,7 @@ package publicProxy import ( "encoding/json" + "errors" "fmt" "io" "net/http" @@ -21,12 +22,12 @@ import ( ) type googleConfigurer struct { - cfg *OauthConfig + cfg *Config googleCfg *googleConfig tls bool } -func newGoogleConfigurer(cfg *OauthConfig, tls bool, v map[string]interface{}) (*googleConfigurer, error) { +func newGoogleConfigurer(cfg *Config, tls bool, v map[string]interface{}) (*googleConfigurer, error) { c := &googleConfigurer{cfg: cfg} googleCfg, err := newGoogleConfig(v) if err != nil { @@ -57,20 +58,20 @@ func (c *googleConfigurer) configure() error { scheme = "https" } - signingKey, err := deriveKey(c.cfg.SigningKey, 32) + signingKey, err := deriveKey(c.cfg.Oauth.SigningKey, 32) if err != nil { return err } - encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32) + encryptionKey, err := deriveKey(c.cfg.Oauth.EncryptionKey, 32) if err != nil { return err } - cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.CookieDomain)) + cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.Oauth.CookieDomain)) rpConfig := &oauth2.Config{ ClientID: c.googleCfg.ClientId, ClientSecret: c.googleCfg.ClientSecret, - RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.EndpointUrl, c.googleCfg.Name), + RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.Oauth.EndpointUrl, c.googleCfg.Name), Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, Endpoint: googleOauth.Endpoint, } @@ -93,7 +94,7 @@ func (c *googleConfigurer) configure() error { targetHost, err := url.QueryUnescape(r.URL.Query().Get("targetHost")) if err != nil { logrus.Errorf("unable to unescape targetHost: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to escape targetHost")), c.cfg.TemplatePath) return } rp.AuthURLHandler(func() string { @@ -103,7 +104,7 @@ func (c *googleConfigurer) configure() error { TargetHost: targetHost, RefreshInterval: r.URL.Query().Get("refreshInterval"), RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.IntermediateLifetime)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.Oauth.IntermediateLifetime)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "zrok", @@ -126,8 +127,8 @@ func (c *googleConfigurer) configure() error { return signingKey, nil }) if err != nil { - logrus.Errorf("after intermediate token parse: %v", err.Error()) - proxyUi.WriteUnauthorized(w) + logrus.Errorf("error parsing intermediate token: %v", err.Error()) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error parsing intermediate token")), c.cfg.TemplatePath) return } @@ -136,14 +137,14 @@ func (c *googleConfigurer) configure() error { refreshInterval = v } else { logrus.Errorf("unable to parse authorization check interval: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse authorization check interval")), c.cfg.TemplatePath) return } resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + url.QueryEscape(tokens.AccessToken)) if err != nil { logrus.Errorf("error getting user info from google: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error getting user info from google")), c.cfg.TemplatePath) return } defer func() { @@ -152,7 +153,7 @@ func (c *googleConfigurer) configure() error { response, err := io.ReadAll(resp.Body) if err != nil { logrus.Errorf("error reading response body: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error reading google response body")), c.cfg.TemplatePath) return } logrus.Debugf("response from google userinfo endpoint: %s", string(response)) @@ -160,12 +161,12 @@ func (c *googleConfigurer) configure() error { err = json.Unmarshal(response, &data) if err != nil { logrus.Errorf("error unmarshalling google oauth response: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error unmarshalling google oauth response")), c.cfg.TemplatePath) return } setSessionCookie(w, sessionCookieRequest{ - cfg: c.cfg, + oauthCfg: c.cfg.Oauth, supportsRefresh: false, email: data.Email, accessToken: tokens.AccessToken, @@ -181,7 +182,7 @@ func (c *googleConfigurer) configure() error { http.Handle(fmt.Sprintf("/%v/auth/callback", c.googleCfg.Name), rp.CodeExchangeHandler(login, provider)) logout := func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(c.cfg.CookieName) + cookie, err := r.Cookie(c.cfg.Oauth.CookieName) if err == nil { tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return signingKey, nil @@ -201,47 +202,47 @@ func (c *googleConfigurer) configure() error { logrus.Infof("revoked google token for '%v'", claims.Email) } else { logrus.Errorf("access token revocation failed with status: %v", resp.StatusCode) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed")), c.cfg.TemplatePath) return } } else { logrus.Errorf("unable to revoke access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to post access token revocation")), c.cfg.TemplatePath) return } } else { logrus.Errorf("unable to decrypt access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) return } } else { logrus.Errorf("expected provider name '%v' got '%v'", c.googleCfg.Name, claims.Email) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider name mismatch")), c.cfg.TemplatePath) return } } else { logrus.Errorf("invalid jwt; unable to parse: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse")), c.cfg.TemplatePath) return } } else { - logrus.Errorf("error getting cookie '%v': %v", c.cfg.CookieName, err) - proxyUi.WriteUnauthorized(w) + logrus.Errorf("error getting cookie '%v': %v", c.cfg.Oauth.CookieName, err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error getting cookie")), c.cfg.TemplatePath) return } http.SetCookie(w, &http.Cookie{ - Name: c.cfg.CookieName, + Name: c.cfg.Oauth.CookieName, Value: "", MaxAge: -1, - Domain: c.cfg.CookieDomain, + Domain: c.cfg.Oauth.CookieDomain, Path: "/", HttpOnly: true, }) redirectURL := r.URL.Query().Get("redirect_url") if redirectURL == "" { - redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.EndpointUrl, c.googleCfg.Name) + redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.Oauth.EndpointUrl, c.googleCfg.Name) } http.Redirect(w, r, redirectURL, http.StatusFound) } diff --git a/endpoints/publicProxy/providerOidc.go b/endpoints/publicProxy/providerOidc.go index fb2ec17a..21f53fb2 100644 --- a/endpoints/publicProxy/providerOidc.go +++ b/endpoints/publicProxy/providerOidc.go @@ -2,6 +2,7 @@ package publicProxy import ( "context" + "errors" "fmt" "net/http" "net/url" @@ -18,12 +19,12 @@ import ( ) type oidcConfigurer struct { - cfg *OauthConfig + cfg *Config oidcCfg *oidcConfig tls bool } -func newOidcConfigurer(cfg *OauthConfig, tls bool, v map[string]interface{}) (*oidcConfigurer, error) { +func newOidcConfigurer(cfg *Config, tls bool, v map[string]interface{}) (*oidcConfigurer, error) { c := &oidcConfigurer{cfg: cfg} oidcCfg, err := newOidcConfig(v) if err != nil { @@ -58,16 +59,16 @@ func (c *oidcConfigurer) configure() error { scheme = "https" } - signingKey, err := deriveKey(c.cfg.SigningKey, 32) + signingKey, err := deriveKey(c.cfg.Oauth.SigningKey, 32) if err != nil { return err } - encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32) + encryptionKey, err := deriveKey(c.cfg.Oauth.EncryptionKey, 32) if err != nil { return err } - cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.CookieDomain)) - redirectUrl := fmt.Sprintf("%v/%v/auth/callback", c.cfg.EndpointUrl, c.oidcCfg.Name) + cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.Oauth.CookieDomain)) + redirectUrl := fmt.Sprintf("%v/%v/auth/callback", c.cfg.Oauth.EndpointUrl, c.oidcCfg.Name) providerOptions := []rp.Option{ rp.WithCookieHandler(cookieHandler), rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)), @@ -92,7 +93,7 @@ func (c *oidcConfigurer) configure() error { targetHost, err := url.QueryUnescape(r.URL.Query().Get("targetHost")) if err != nil { logrus.Errorf("unable to unescape 'targetHost': %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to unescape targetHost")), c.cfg.TemplatePath) return } state := func() string { @@ -102,7 +103,7 @@ func (c *oidcConfigurer) configure() error { TargetHost: targetHost, RefreshInterval: r.URL.Query().Get("refreshInterval"), RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.IntermediateLifetime)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.Oauth.IntermediateLifetime)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "zrok", @@ -134,14 +135,14 @@ func (c *oidcConfigurer) configure() error { targetHost, err := url.QueryUnescape(r.URL.Query().Get("targetHost")) if err != nil { logrus.Errorf("unable to unescape 'targetHost': %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to unescape targetHost")), c.cfg.TemplatePath) return } - cookie, err := r.Cookie(c.cfg.CookieName) + cookie, err := r.Cookie(c.cfg.Oauth.CookieName) if err != nil { - logrus.Errorf("unable to get 'zrok-access' cookie: %v", err) - proxyUi.WriteUnauthorized(w) + logrus.Errorf("unable to get auth session cookie: %v", err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to get auth session cookie")), c.cfg.TemplatePath) return } @@ -150,33 +151,33 @@ func (c *oidcConfigurer) configure() error { }) if err != nil { logrus.Errorf("unable to parse jwt: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse jwt")), c.cfg.TemplatePath) return } claims := tkn.Claims.(*zrokClaims) if claims.Provider != c.oidcCfg.Name { - logrus.Error("token validation failed") - proxyUi.WriteUnauthorized(w) + logrus.Error("token provider mismatch") + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("token provider mismatch")), c.cfg.TemplatePath) return } accessToken, err := decryptToken(claims.AccessToken, encryptionKey) if err != nil { logrus.Errorf("unable to decrypt access token: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) return } newTokens, err := rp.RefreshTokens[*oidc.IDTokenClaims](context.Background(), provider, accessToken, "", "") if err != nil { logrus.Errorf("unable to refresh tokens: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to refresh tokens")), c.cfg.TemplatePath) return } setSessionCookie(w, sessionCookieRequest{ - cfg: c.cfg, + oauthCfg: c.cfg.Oauth, supportsRefresh: true, email: claims.Email, accessToken: newTokens.AccessToken, @@ -197,7 +198,7 @@ func (c *oidcConfigurer) configure() error { }) if err != nil { logrus.Errorf("unable to parse intermediate JWT: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse intermediate jwt")), c.cfg.TemplatePath) return } @@ -206,12 +207,12 @@ func (c *oidcConfigurer) configure() error { refreshInterval = v } else { logrus.Errorf("unable to parse authorization check interval: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(info.Email).WithError(errors.New("unable to parse authorization check interval")), c.cfg.TemplatePath) return } setSessionCookie(w, sessionCookieRequest{ - cfg: c.cfg, + oauthCfg: c.cfg.Oauth, supportsRefresh: true, email: info.Email, accessToken: tokens.AccessToken, @@ -227,7 +228,7 @@ func (c *oidcConfigurer) configure() error { http.Handle(fmt.Sprintf("/%v/auth/callback", c.oidcCfg.Name), rp.CodeExchangeHandler(rp.UserinfoCallback(login), provider)) logout := func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(c.cfg.CookieName) + cookie, err := r.Cookie(c.cfg.Oauth.CookieName) if err == nil { tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return signingKey, nil @@ -241,42 +242,42 @@ func (c *oidcConfigurer) configure() error { logrus.Infof("revoked access token for '%v'", claims.Email) } else { logrus.Errorf("access token revocation failed: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed")), c.cfg.TemplatePath) return } } else { logrus.Errorf("unable to decrypt access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) return } } else { logrus.Errorf("expected provider name '%v' got '%v'", c.oidcCfg.Name, claims.Email) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider mismatch")), c.cfg.TemplatePath) return } } else { logrus.Errorf("invalid jwt; unable to parse: %v", err) - proxyUi.WriteUnauthorized(w) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse")), c.cfg.TemplatePath) return } } else { - logrus.Errorf("error getting cookie '%v': %v", c.cfg.CookieName, err) - proxyUi.WriteUnauthorized(w) + logrus.Errorf("error getting cookie '%v': %v", c.cfg.Oauth.CookieName, err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid cookie")), c.cfg.TemplatePath) return } http.SetCookie(w, &http.Cookie{ - Name: c.cfg.CookieName, + Name: c.cfg.Oauth.CookieName, Value: "", MaxAge: -1, - Domain: c.cfg.CookieDomain, + Domain: c.cfg.Oauth.CookieDomain, Path: "/", HttpOnly: true, }) redirectURL := r.URL.Query().Get("redirect_url") if redirectURL == "" { - redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.EndpointUrl, c.oidcCfg.Name) + redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.Oauth.EndpointUrl, c.oidcCfg.Name) } http.Redirect(w, r, redirectURL, http.StatusFound) } From facbbc1a5295ac5b633ba1c3160ab02505c96267 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 15:28:16 -0400 Subject: [PATCH 3/9] health check -> template (#1012) --- endpoints/proxyUi/embed.go | 2 +- endpoints/proxyUi/health.go | 22 -- endpoints/proxyUi/health.html | 400 ---------------------------------- endpoints/proxyUi/template.go | 4 + endpoints/publicProxy/http.go | 2 +- 5 files changed, 6 insertions(+), 424 deletions(-) delete mode 100644 endpoints/proxyUi/health.go delete mode 100644 endpoints/proxyUi/health.html diff --git a/endpoints/proxyUi/embed.go b/endpoints/proxyUi/embed.go index 78f901d3..e695c223 100644 --- a/endpoints/proxyUi/embed.go +++ b/endpoints/proxyUi/embed.go @@ -2,5 +2,5 @@ package proxyUi import "embed" -//go:embed health.html intersititial.html template.html +//go:embed intersititial.html template.html var FS embed.FS diff --git a/endpoints/proxyUi/health.go b/endpoints/proxyUi/health.go deleted file mode 100644 index 69422ca3..00000000 --- a/endpoints/proxyUi/health.go +++ /dev/null @@ -1,22 +0,0 @@ -package proxyUi - -import ( - "net/http" - - "github.com/sirupsen/logrus" -) - -func WriteHealthOk(w http.ResponseWriter) { - if data, err := FS.ReadFile("health.html"); err == nil { - w.WriteHeader(http.StatusOK) - n, err := w.Write(data) - if n != len(data) { - logrus.Errorf("short write") - return - } - if err != nil { - logrus.Error(err) - return - } - } -} diff --git a/endpoints/proxyUi/health.html b/endpoints/proxyUi/health.html deleted file mode 100644 index 5a750e53..00000000 --- a/endpoints/proxyUi/health.html +++ /dev/null @@ -1,400 +0,0 @@ - - - - - - - - - - - zrok - - - -
- -
-
-

zrok frontend health: ok

-
-
-
- - diff --git a/endpoints/proxyUi/template.go b/endpoints/proxyUi/template.go index 32f0d0c5..3572b77c 100644 --- a/endpoints/proxyUi/template.go +++ b/endpoints/proxyUi/template.go @@ -14,6 +14,10 @@ var externalTemplate []byte type VariableData map[string]interface{} +func WriteHealthOk(w http.ResponseWriter, templatePath string) { + WriteTemplate(w, http.StatusOK, RequiredData("healthy", "healthy"), templatePath) +} + func WriteBadGateway(w http.ResponseWriter, variableData VariableData, templatePath string) { WriteTemplate(w, http.StatusBadGateway, variableData, templatePath) } diff --git a/endpoints/publicProxy/http.go b/endpoints/publicProxy/http.go index 5f4ef908..18d8ef70 100644 --- a/endpoints/publicProxy/http.go +++ b/endpoints/publicProxy/http.go @@ -157,7 +157,7 @@ func shareHandler(handler http.Handler, cfg *Config, signingKey []byte, ctx ziti shrToken := resolveService(cfg.HostMatch, r.Host) if shrToken == "" { logrus.Debugf("host '%v' did not match host match, returning health check", r.Host) - proxyUi.WriteHealthOk(w) + proxyUi.WriteHealthOk(w, cfg.TemplatePath) return } From bdb8d1882c51fe60c0e3579d6c04f755686e5e25 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 15:31:08 -0400 Subject: [PATCH 4/9] changelog (#1012) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe71af2..a5a3319f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ FEATURE: Rewritten and improved `publicProxy` package (`zrok access public`), with support for extensible OAuth-compliant identity providers. The `publicProxy` configuration now supports any number of configured OAuth-compliant providers (rather than just a single `google` provider and/or a single `github` provider). Also includes a new OIDC-compliant generic IDP provider integration. Improvements to authentication flows and security all around. See the updated guide on using OAuth-based identity providers with the zrok public frontend (https://github.com/openziti/zrok/issues/968) +FEATURE: Templatized and improved static pages (not found/404, unauthorized/401, health check, etc.) used by the public frontend. Consolidated variable data using golang `text/template` so that static `proxyUi` package can display additional error information and provide extension points for replacing all of the templated content with external files (https://github.com/openziti/zrok/issues/1012) + FIX: Invoking `/agent/*` endpoints to remotely manage agents with remoting was causing a new API session to be allocated in the ziti controller for each request. A slightly different strategy was employed for embedding the ziti SDK into the zrok controller that should mitigate this (https://github.com/openziti/zrok/issues/1023) ## v1.0.8 From 8f93bee33da5bb07066674e0ad074895d4d4bb74 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 15:59:06 -0400 Subject: [PATCH 5/9] stop passing the template path around; silly (#1012) --- endpoints/proxy/frontend.go | 8 ++- endpoints/proxyUi/template.go | 68 ++++++++++++------------- endpoints/publicProxy/authOAuth.go | 4 +- endpoints/publicProxy/config.go | 8 +-- endpoints/publicProxy/cookies.go | 7 ++- endpoints/publicProxy/http.go | 17 ++++--- endpoints/publicProxy/providerGithub.go | 54 ++++++++++---------- endpoints/publicProxy/providerGoogle.go | 50 +++++++++--------- endpoints/publicProxy/providerOidc.go | 58 ++++++++++----------- 9 files changed, 138 insertions(+), 136 deletions(-) diff --git a/endpoints/proxy/frontend.go b/endpoints/proxy/frontend.go index 7992194e..1d671625 100644 --- a/endpoints/proxy/frontend.go +++ b/endpoints/proxy/frontend.go @@ -25,7 +25,6 @@ type FrontendConfig struct { ShrToken string Address string ResponseHeaders []string - TemplatePath string Tls *endpoints.TlsConfig RequestsChan chan *endpoints.Request SuperNetwork bool @@ -137,7 +136,6 @@ func newServiceProxy(cfg *FrontendConfig, ctx ziti.Context) (*httputil.ReversePr "bad gateway!", fmt.Sprintf("bad gateway for share %v!", cfg.ShrToken), ).WithError(err), - cfg.TemplatePath, ) } return proxy, nil @@ -238,15 +236,15 @@ func authHandler(shrToken string, handler http.Handler, realm string, cfg *Front } } else { logrus.Warnf("%v -> no auth scheme for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken)) } } else { logrus.Warnf("%v -> no proxy config for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken)) } } else { logrus.Warnf("%v -> service '%v' not found", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken)) } } } diff --git a/endpoints/proxyUi/template.go b/endpoints/proxyUi/template.go index 3572b77c..3fdaaf3b 100644 --- a/endpoints/proxyUi/template.go +++ b/endpoints/proxyUi/template.go @@ -7,19 +7,43 @@ import ( "os" "text/template" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) -var externalTemplate []byte +var tmpl *template.Template + +func init() { + if data, err := FS.ReadFile("template.html"); err == nil { + tmpl, err = template.New("template").Parse(string(data)) + if err != nil { + panic(errors.Wrap(err, "unable to parse embedded template 'template.html'")) + } + } else { + panic(errors.Wrap(err, "unable to load embedded template 'template.html'")) + } +} type VariableData map[string]interface{} -func WriteHealthOk(w http.ResponseWriter, templatePath string) { - WriteTemplate(w, http.StatusOK, RequiredData("healthy", "healthy"), templatePath) +func ReplaceTemplate(path string) error { + if f, err := os.ReadFile(path); err == nil { + tmpl, err = template.New("template").Parse(string(f)) + if err != nil { + panic(errors.Wrapf(err, "unable to parse template '%v'", path)) + } + } else { + return errors.Wrapf(err, "error reading template from '%v'", path) + } + return nil } -func WriteBadGateway(w http.ResponseWriter, variableData VariableData, templatePath string) { - WriteTemplate(w, http.StatusBadGateway, variableData, templatePath) +func WriteHealthOk(w http.ResponseWriter) { + WriteTemplate(w, http.StatusOK, RequiredData("healthy", "healthy")) +} + +func WriteBadGateway(w http.ResponseWriter, variableData VariableData) { + WriteTemplate(w, http.StatusBadGateway, variableData) } func RequiredData(title, banner string) VariableData { @@ -41,8 +65,8 @@ func NotFoundData(shareToken string) VariableData { ) } -func WriteNotFound(w http.ResponseWriter, variableData VariableData, templatePath string) { - WriteTemplate(w, http.StatusNotFound, variableData, templatePath) +func WriteNotFound(w http.ResponseWriter, variableData VariableData) { + WriteTemplate(w, http.StatusNotFound, variableData) } func UnauthorizedData() VariableData { @@ -59,34 +83,11 @@ func UnauthorizedUser(user string) VariableData { ) } -func WriteUnauthorized(w http.ResponseWriter, variableData VariableData, templatePath string) { - WriteTemplate(w, http.StatusUnauthorized, variableData, templatePath) +func WriteUnauthorized(w http.ResponseWriter, variableData VariableData) { + WriteTemplate(w, http.StatusUnauthorized, variableData) } -func WriteTemplate(w http.ResponseWriter, statusCode int, variableData VariableData, templatePath string) { - if templatePath != "" && externalTemplate == nil { - if f, err := os.ReadFile(templatePath); err == nil { - externalTemplate = f - } else { - logrus.Errorf("error reading proxyUi template from '%v': %v", templatePath, err) - } - } - var templateData = externalTemplate - if templateData == nil { - if f, err := FS.ReadFile("template.html"); err == nil { - templateData = f - } else { - logrus.Errorf("error reading embedded proxyUi template 'template.html': %v", err) - } - } - - tmpl, err := template.New("template").Parse(string(templateData)) - if err != nil { - logrus.Errorf("failed to parse template: %v", err) - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - +func WriteTemplate(w http.ResponseWriter, statusCode int, variableData VariableData) { var buf bytes.Buffer if err := tmpl.Execute(&buf, variableData); err != nil { logrus.Errorf("failed to execute template: %v", err) @@ -94,7 +95,6 @@ func WriteTemplate(w http.ResponseWriter, statusCode int, variableData VariableD return } - // Write the response w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(statusCode) diff --git a/endpoints/publicProxy/authOAuth.go b/endpoints/publicProxy/authOAuth.go index 5925bc36..293a2ed4 100644 --- a/endpoints/publicProxy/authOAuth.go +++ b/endpoints/publicProxy/authOAuth.go @@ -118,7 +118,7 @@ func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[st if err != nil { err := fmt.Errorf("invalid email address pattern glob '%v': %v", pattern, err) logrus.Error(err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(err), cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(err)) return false } if match.Match(claims.Email) { @@ -127,7 +127,7 @@ func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[st } } logrus.Warnf("unauthorized email '%v'", claims.Email) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email), cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email)) return false } return true diff --git a/endpoints/publicProxy/config.go b/endpoints/publicProxy/config.go index 4a295e88..2782ee69 100644 --- a/endpoints/publicProxy/config.go +++ b/endpoints/publicProxy/config.go @@ -102,7 +102,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { if t, found := mv["type"]; found { switch t { case "github": - cfger, err := newGithubConfigurer(cfg, tls, mv) + cfger, err := newGithubConfigurer(cfg.Oauth, tls, mv) if err != nil { return err } @@ -111,7 +111,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { } case "google": - cfger, err := newGoogleConfigurer(cfg, tls, mv) + cfger, err := newGoogleConfigurer(cfg.Oauth, tls, mv) if err != nil { return err } @@ -120,7 +120,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { } case "oidc": - cfger, err := newOidcConfigurer(cfg, tls, mv) + cfger, err := newOidcConfigurer(cfg.Oauth, tls, mv) if err != nil { return err } @@ -140,7 +140,7 @@ func configureOauth(ctx context.Context, cfg *Config, tls bool) error { } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData(), cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData()) }) zhttp.StartServer(ctx, cfg.Oauth.BindAddress) diff --git a/endpoints/publicProxy/cookies.go b/endpoints/publicProxy/cookies.go index 6e3ae2d2..b7ed3df1 100644 --- a/endpoints/publicProxy/cookies.go +++ b/endpoints/publicProxy/cookies.go @@ -12,7 +12,6 @@ import ( ) type sessionCookieRequest struct { - cfg *Config oauthCfg *OauthConfig supportsRefresh bool email string @@ -29,7 +28,7 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { if targetHost == "" { err := errors.New("targetHost claim must not be empty") logrus.Error(err) - proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err), req.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err)) return } targetHost = strings.Split(targetHost, "/")[0] @@ -37,7 +36,7 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { encryptedAccessToken, err := encryptToken(req.accessToken, req.encryptionKey) if err != nil { logrus.Errorf("failed to encrypt access token: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(errors.New("failed to encrypt access token")), req.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(errors.New("failed to encrypt access token"))) return } @@ -56,7 +55,7 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { sTkn, err := tkn.SignedString(req.signingKey) if err != nil { logrus.Errorf("error signing jwt: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(req.email).WithError(errors.New("error signing jwt")), req.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(req.email).WithError(errors.New("error signing jwt"))) return } diff --git a/endpoints/publicProxy/http.go b/endpoints/publicProxy/http.go index 18d8ef70..d3d360ce 100644 --- a/endpoints/publicProxy/http.go +++ b/endpoints/publicProxy/http.go @@ -32,6 +32,12 @@ func NewHTTP(cfg *Config) (*HttpFrontend, error) { return nil, err } + if cfg.TemplatePath != "" { + if err := proxyUi.ReplaceTemplate(cfg.TemplatePath); err != nil { + return nil, err + } + } + root, err := environment.LoadRoot() if err != nil { return nil, errors.Wrap(err, "error loading environment root") @@ -111,7 +117,6 @@ func newServiceProxy(cfg *Config, ctx ziti.Context) (*httputil.ReverseProxy, err "bad gateway!", "bad gateway!", ), - cfg.TemplatePath, ) } return proxy, nil @@ -157,21 +162,21 @@ func shareHandler(handler http.Handler, cfg *Config, signingKey []byte, ctx ziti shrToken := resolveService(cfg.HostMatch, r.Host) if shrToken == "" { logrus.Debugf("host '%v' did not match host match, returning health check", r.Host) - proxyUi.WriteHealthOk(w, cfg.TemplatePath) + proxyUi.WriteHealthOk(w) return } svc, found := endpoints.GetRefreshedService(shrToken, ctx) if !found { logrus.Warnf("%v -> service '%v' not found", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken)) return } svcCfg, found := svc.Config[sdk.ZrokProxyConfig] if !found { logrus.Warnf("%v -> no proxy config for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken)) return } @@ -182,7 +187,7 @@ func shareHandler(handler http.Handler, cfg *Config, signingKey []byte, ctx ziti authScheme, found := svcCfg["auth_scheme"] if !found { logrus.Warnf("%v -> no auth scheme for '%v'", r.RemoteAddr, shrToken) - proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken), cfg.TemplatePath) + proxyUi.WriteNotFound(w, proxyUi.NotFoundData(shrToken)) return } @@ -208,7 +213,7 @@ func shareHandler(handler http.Handler, cfg *Config, signingKey []byte, ctx ziti default: err := fmt.Errorf("invalid auth scheme '%v'", authScheme) logrus.Error(err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(err), cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(err)) } } } diff --git a/endpoints/publicProxy/providerGithub.go b/endpoints/publicProxy/providerGithub.go index dd920739..144a2b44 100644 --- a/endpoints/publicProxy/providerGithub.go +++ b/endpoints/publicProxy/providerGithub.go @@ -23,12 +23,12 @@ import ( ) type githubConfigurer struct { - cfg *Config + cfg *OauthConfig githubCfg *githubConfig tls bool } -func newGithubConfigurer(cfg *Config, tls bool, v map[string]interface{}) (*githubConfigurer, error) { +func newGithubConfigurer(cfg *OauthConfig, tls bool, v map[string]interface{}) (*githubConfigurer, error) { c := &githubConfigurer{cfg: cfg} githubCfg, err := newGithubConfig(v) if err != nil { @@ -59,20 +59,20 @@ func (c *githubConfigurer) configure() error { scheme = "https" } - signingKey, err := deriveKey(c.cfg.Oauth.SigningKey, 32) + signingKey, err := deriveKey(c.cfg.SigningKey, 32) if err != nil { return err } - encryptionKey, err := deriveKey(c.cfg.Oauth.EncryptionKey, 32) + encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32) if err != nil { return err } - cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.Oauth.CookieDomain)) + cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.CookieDomain)) rpConfig := &oauth2.Config{ ClientID: c.githubCfg.ClientId, ClientSecret: c.githubCfg.ClientSecret, - RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.Oauth.EndpointUrl, c.githubCfg.Name), + RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.EndpointUrl, c.githubCfg.Name), Scopes: []string{"user:email"}, Endpoint: githubOAuth.Endpoint, } @@ -98,7 +98,7 @@ func (c *githubConfigurer) configure() error { if err != nil { err := fmt.Errorf("unable to unescape targetHost: %v", err) logrus.Error(err) - proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err)) return } rp.AuthURLHandler(func() string { @@ -108,7 +108,7 @@ func (c *githubConfigurer) configure() error { TargetHost: targetHost, RefreshInterval: r.URL.Query().Get("refreshInterval"), RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.Oauth.IntermediateLifetime)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.IntermediateLifetime)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "zrok", @@ -133,7 +133,7 @@ func (c *githubConfigurer) configure() error { if err != nil { errOut := errors.Wrap(err, "error parsing intermediate token") logrus.Error(errOut) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut)) return } @@ -143,7 +143,7 @@ func (c *githubConfigurer) configure() error { } else { errOut := errors.Wrapf(err, "unable to parse authorization check interval '%v'", token.Claims.(*IntermediateJWT).RefreshInterval) logrus.Error(errOut) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut)) return } @@ -151,7 +151,7 @@ func (c *githubConfigurer) configure() error { if err != nil { errOut := errors.Wrap(err, "error parsing github url") logrus.Error(errOut) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut)) return } req := &http.Request{ @@ -164,7 +164,7 @@ func (c *githubConfigurer) configure() error { if err != nil { errOut := errors.Wrap(err, "error getting user info from github") logrus.Error(errOut) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut)) return } defer func() { @@ -174,7 +174,7 @@ func (c *githubConfigurer) configure() error { if err != nil { errOut := errors.Wrap(err, "error reading response body from github") logrus.Error(errOut) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut)) return } var rDat []githubUserResp @@ -182,7 +182,7 @@ func (c *githubConfigurer) configure() error { if err != nil { errOut := errors.Wrap(err, "error unmarshalling response from github") logrus.Error(errOut) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errOut)) return } @@ -195,7 +195,7 @@ func (c *githubConfigurer) configure() error { } setSessionCookie(w, sessionCookieRequest{ - oauthCfg: c.cfg.Oauth, + oauthCfg: c.cfg, supportsRefresh: false, email: primaryEmail, accessToken: tokens.AccessToken, @@ -211,7 +211,7 @@ func (c *githubConfigurer) configure() error { http.Handle(fmt.Sprintf("/%v/auth/callback", c.githubCfg.Name), rp.CodeExchangeHandler(login, provider)) logout := func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(c.cfg.Oauth.CookieName) + cookie, err := r.Cookie(c.cfg.CookieName) if err == nil { tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return signingKey, nil @@ -226,7 +226,7 @@ func (c *githubConfigurer) configure() error { strings.NewReader(fmt.Sprintf(`{"access_token":"%s"}`, accessToken))) if err != nil { logrus.Errorf("error creating access token delete request for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("error creating access token delete request")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("error creating access token delete request"))) return } @@ -236,7 +236,7 @@ func (c *githubConfigurer) configure() error { resp, err := http.DefaultClient.Do(req) if err != nil { logrus.Errorf("error invoking access token delete request for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("error executing access token delete request")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("error executing access token delete request"))) return } defer resp.Body.Close() @@ -245,42 +245,42 @@ func (c *githubConfigurer) configure() error { logrus.Infof("revoked github access token for '%v'", claims.Email) } else { logrus.Errorf("access token revocation failed with status: %v", resp.StatusCode) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed"))) return } } else { logrus.Errorf("unable to decrypt access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token"))) return } } else { logrus.Errorf("expected provider name '%v' got '%v'", c.githubCfg.Name, claims.Email) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider name mismatch")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider name mismatch"))) return } } else { logrus.Errorf("invalid jwt; unable to parse: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse"))) return } } else { - logrus.Errorf("error getting cookie '%v': %v", c.cfg.Oauth.CookieName, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid cookie")), c.cfg.TemplatePath) + logrus.Errorf("error getting cookie '%v': %v", c.cfg.CookieName, err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid cookie"))) return } http.SetCookie(w, &http.Cookie{ - Name: c.cfg.Oauth.CookieName, + Name: c.cfg.CookieName, Value: "", MaxAge: -1, - Domain: c.cfg.Oauth.CookieDomain, + Domain: c.cfg.CookieDomain, Path: "/", HttpOnly: true, }) redirectURL := r.URL.Query().Get("redirect_url") if redirectURL == "" { - redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.Oauth.EndpointUrl, c.githubCfg.Name) + redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.EndpointUrl, c.githubCfg.Name) } http.Redirect(w, r, redirectURL, http.StatusFound) } diff --git a/endpoints/publicProxy/providerGoogle.go b/endpoints/publicProxy/providerGoogle.go index c30e205f..0dd283f3 100644 --- a/endpoints/publicProxy/providerGoogle.go +++ b/endpoints/publicProxy/providerGoogle.go @@ -22,12 +22,12 @@ import ( ) type googleConfigurer struct { - cfg *Config + cfg *OauthConfig googleCfg *googleConfig tls bool } -func newGoogleConfigurer(cfg *Config, tls bool, v map[string]interface{}) (*googleConfigurer, error) { +func newGoogleConfigurer(cfg *OauthConfig, tls bool, v map[string]interface{}) (*googleConfigurer, error) { c := &googleConfigurer{cfg: cfg} googleCfg, err := newGoogleConfig(v) if err != nil { @@ -58,20 +58,20 @@ func (c *googleConfigurer) configure() error { scheme = "https" } - signingKey, err := deriveKey(c.cfg.Oauth.SigningKey, 32) + signingKey, err := deriveKey(c.cfg.SigningKey, 32) if err != nil { return err } - encryptionKey, err := deriveKey(c.cfg.Oauth.EncryptionKey, 32) + encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32) if err != nil { return err } - cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.Oauth.CookieDomain)) + cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.CookieDomain)) rpConfig := &oauth2.Config{ ClientID: c.googleCfg.ClientId, ClientSecret: c.googleCfg.ClientSecret, - RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.Oauth.EndpointUrl, c.googleCfg.Name), + RedirectURL: fmt.Sprintf("%v/%v/auth/callback", c.cfg.EndpointUrl, c.googleCfg.Name), Scopes: []string{"https://www.googleapis.com/auth/userinfo.email"}, Endpoint: googleOauth.Endpoint, } @@ -94,7 +94,7 @@ func (c *googleConfigurer) configure() error { targetHost, err := url.QueryUnescape(r.URL.Query().Get("targetHost")) if err != nil { logrus.Errorf("unable to unescape targetHost: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to escape targetHost")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to escape targetHost"))) return } rp.AuthURLHandler(func() string { @@ -104,7 +104,7 @@ func (c *googleConfigurer) configure() error { TargetHost: targetHost, RefreshInterval: r.URL.Query().Get("refreshInterval"), RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.Oauth.IntermediateLifetime)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.IntermediateLifetime)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "zrok", @@ -128,7 +128,7 @@ func (c *googleConfigurer) configure() error { }) if err != nil { logrus.Errorf("error parsing intermediate token: %v", err.Error()) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error parsing intermediate token")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error parsing intermediate token"))) return } @@ -137,14 +137,14 @@ func (c *googleConfigurer) configure() error { refreshInterval = v } else { logrus.Errorf("unable to parse authorization check interval: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse authorization check interval")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse authorization check interval"))) return } resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + url.QueryEscape(tokens.AccessToken)) if err != nil { logrus.Errorf("error getting user info from google: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error getting user info from google")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error getting user info from google"))) return } defer func() { @@ -153,7 +153,7 @@ func (c *googleConfigurer) configure() error { response, err := io.ReadAll(resp.Body) if err != nil { logrus.Errorf("error reading response body: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error reading google response body")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error reading google response body"))) return } logrus.Debugf("response from google userinfo endpoint: %s", string(response)) @@ -161,12 +161,12 @@ func (c *googleConfigurer) configure() error { err = json.Unmarshal(response, &data) if err != nil { logrus.Errorf("error unmarshalling google oauth response: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error unmarshalling google oauth response")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error unmarshalling google oauth response"))) return } setSessionCookie(w, sessionCookieRequest{ - oauthCfg: c.cfg.Oauth, + oauthCfg: c.cfg, supportsRefresh: false, email: data.Email, accessToken: tokens.AccessToken, @@ -182,7 +182,7 @@ func (c *googleConfigurer) configure() error { http.Handle(fmt.Sprintf("/%v/auth/callback", c.googleCfg.Name), rp.CodeExchangeHandler(login, provider)) logout := func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(c.cfg.Oauth.CookieName) + cookie, err := r.Cookie(c.cfg.CookieName) if err == nil { tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return signingKey, nil @@ -202,47 +202,47 @@ func (c *googleConfigurer) configure() error { logrus.Infof("revoked google token for '%v'", claims.Email) } else { logrus.Errorf("access token revocation failed with status: %v", resp.StatusCode) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed"))) return } } else { logrus.Errorf("unable to revoke access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to post access token revocation")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to post access token revocation"))) return } } else { logrus.Errorf("unable to decrypt access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token"))) return } } else { logrus.Errorf("expected provider name '%v' got '%v'", c.googleCfg.Name, claims.Email) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider name mismatch")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider name mismatch"))) return } } else { logrus.Errorf("invalid jwt; unable to parse: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse"))) return } } else { - logrus.Errorf("error getting cookie '%v': %v", c.cfg.Oauth.CookieName, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error getting cookie")), c.cfg.TemplatePath) + logrus.Errorf("error getting cookie '%v': %v", c.cfg.CookieName, err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("error getting cookie"))) return } http.SetCookie(w, &http.Cookie{ - Name: c.cfg.Oauth.CookieName, + Name: c.cfg.CookieName, Value: "", MaxAge: -1, - Domain: c.cfg.Oauth.CookieDomain, + Domain: c.cfg.CookieDomain, Path: "/", HttpOnly: true, }) redirectURL := r.URL.Query().Get("redirect_url") if redirectURL == "" { - redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.Oauth.EndpointUrl, c.googleCfg.Name) + redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.EndpointUrl, c.googleCfg.Name) } http.Redirect(w, r, redirectURL, http.StatusFound) } diff --git a/endpoints/publicProxy/providerOidc.go b/endpoints/publicProxy/providerOidc.go index 21f53fb2..1bfb9364 100644 --- a/endpoints/publicProxy/providerOidc.go +++ b/endpoints/publicProxy/providerOidc.go @@ -19,12 +19,12 @@ import ( ) type oidcConfigurer struct { - cfg *Config + cfg *OauthConfig oidcCfg *oidcConfig tls bool } -func newOidcConfigurer(cfg *Config, tls bool, v map[string]interface{}) (*oidcConfigurer, error) { +func newOidcConfigurer(cfg *OauthConfig, tls bool, v map[string]interface{}) (*oidcConfigurer, error) { c := &oidcConfigurer{cfg: cfg} oidcCfg, err := newOidcConfig(v) if err != nil { @@ -59,16 +59,16 @@ func (c *oidcConfigurer) configure() error { scheme = "https" } - signingKey, err := deriveKey(c.cfg.Oauth.SigningKey, 32) + signingKey, err := deriveKey(c.cfg.SigningKey, 32) if err != nil { return err } - encryptionKey, err := deriveKey(c.cfg.Oauth.EncryptionKey, 32) + encryptionKey, err := deriveKey(c.cfg.EncryptionKey, 32) if err != nil { return err } - cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.Oauth.CookieDomain)) - redirectUrl := fmt.Sprintf("%v/%v/auth/callback", c.cfg.Oauth.EndpointUrl, c.oidcCfg.Name) + cookieHandler := zhttp.NewCookieHandler(signingKey, encryptionKey, zhttp.WithUnsecure(), zhttp.WithDomain(c.cfg.CookieDomain)) + redirectUrl := fmt.Sprintf("%v/%v/auth/callback", c.cfg.EndpointUrl, c.oidcCfg.Name) providerOptions := []rp.Option{ rp.WithCookieHandler(cookieHandler), rp.WithVerifierOpts(rp.WithIssuedAtOffset(5 * time.Second)), @@ -93,7 +93,7 @@ func (c *oidcConfigurer) configure() error { targetHost, err := url.QueryUnescape(r.URL.Query().Get("targetHost")) if err != nil { logrus.Errorf("unable to unescape 'targetHost': %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to unescape targetHost")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to unescape targetHost"))) return } state := func() string { @@ -103,7 +103,7 @@ func (c *oidcConfigurer) configure() error { TargetHost: targetHost, RefreshInterval: r.URL.Query().Get("refreshInterval"), RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.Oauth.IntermediateLifetime)), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(c.cfg.IntermediateLifetime)), IssuedAt: jwt.NewNumericDate(time.Now()), NotBefore: jwt.NewNumericDate(time.Now()), Issuer: "zrok", @@ -135,14 +135,14 @@ func (c *oidcConfigurer) configure() error { targetHost, err := url.QueryUnescape(r.URL.Query().Get("targetHost")) if err != nil { logrus.Errorf("unable to unescape 'targetHost': %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to unescape targetHost")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to unescape targetHost"))) return } - cookie, err := r.Cookie(c.cfg.Oauth.CookieName) + cookie, err := r.Cookie(c.cfg.CookieName) if err != nil { logrus.Errorf("unable to get auth session cookie: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to get auth session cookie")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to get auth session cookie"))) return } @@ -151,33 +151,33 @@ func (c *oidcConfigurer) configure() error { }) if err != nil { logrus.Errorf("unable to parse jwt: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse jwt")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse jwt"))) return } claims := tkn.Claims.(*zrokClaims) if claims.Provider != c.oidcCfg.Name { logrus.Error("token provider mismatch") - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("token provider mismatch")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("token provider mismatch"))) return } accessToken, err := decryptToken(claims.AccessToken, encryptionKey) if err != nil { logrus.Errorf("unable to decrypt access token: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token"))) return } newTokens, err := rp.RefreshTokens[*oidc.IDTokenClaims](context.Background(), provider, accessToken, "", "") if err != nil { logrus.Errorf("unable to refresh tokens: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to refresh tokens")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to refresh tokens"))) return } setSessionCookie(w, sessionCookieRequest{ - oauthCfg: c.cfg.Oauth, + oauthCfg: c.cfg, supportsRefresh: true, email: claims.Email, accessToken: newTokens.AccessToken, @@ -198,7 +198,7 @@ func (c *oidcConfigurer) configure() error { }) if err != nil { logrus.Errorf("unable to parse intermediate JWT: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse intermediate jwt")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("unable to parse intermediate jwt"))) return } @@ -207,12 +207,12 @@ func (c *oidcConfigurer) configure() error { refreshInterval = v } else { logrus.Errorf("unable to parse authorization check interval: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(info.Email).WithError(errors.New("unable to parse authorization check interval")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(info.Email).WithError(errors.New("unable to parse authorization check interval"))) return } setSessionCookie(w, sessionCookieRequest{ - oauthCfg: c.cfg.Oauth, + oauthCfg: c.cfg, supportsRefresh: true, email: info.Email, accessToken: tokens.AccessToken, @@ -228,7 +228,7 @@ func (c *oidcConfigurer) configure() error { http.Handle(fmt.Sprintf("/%v/auth/callback", c.oidcCfg.Name), rp.CodeExchangeHandler(rp.UserinfoCallback(login), provider)) logout := func(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(c.cfg.Oauth.CookieName) + cookie, err := r.Cookie(c.cfg.CookieName) if err == nil { tkn, err := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return signingKey, nil @@ -242,42 +242,42 @@ func (c *oidcConfigurer) configure() error { logrus.Infof("revoked access token for '%v'", claims.Email) } else { logrus.Errorf("access token revocation failed: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("access token revocation failed"))) return } } else { logrus.Errorf("unable to decrypt access token for '%v': %v", claims.Email, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("unable to decrypt access token"))) return } } else { logrus.Errorf("expected provider name '%v' got '%v'", c.oidcCfg.Name, claims.Email) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider mismatch")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedUser(claims.Email).WithError(errors.New("provider mismatch"))) return } } else { logrus.Errorf("invalid jwt; unable to parse: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse")), c.cfg.TemplatePath) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid jwt; unable to parse"))) return } } else { - logrus.Errorf("error getting cookie '%v': %v", c.cfg.Oauth.CookieName, err) - proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid cookie")), c.cfg.TemplatePath) + logrus.Errorf("error getting cookie '%v': %v", c.cfg.CookieName, err) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("invalid cookie"))) return } http.SetCookie(w, &http.Cookie{ - Name: c.cfg.Oauth.CookieName, + Name: c.cfg.CookieName, Value: "", MaxAge: -1, - Domain: c.cfg.Oauth.CookieDomain, + Domain: c.cfg.CookieDomain, Path: "/", HttpOnly: true, }) redirectURL := r.URL.Query().Get("redirect_url") if redirectURL == "" { - redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.Oauth.EndpointUrl, c.oidcCfg.Name) + redirectURL = fmt.Sprintf("%s/%s/login", c.cfg.EndpointUrl, c.oidcCfg.Name) } http.Redirect(w, r, redirectURL, http.StatusFound) } From 528c1c477163a2d24fbaad2f7e6c4120e86f127d Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 16:00:18 -0400 Subject: [PATCH 6/9] revert; no pass path (#1012) --- endpoints/publicProxy/authOAuth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/endpoints/publicProxy/authOAuth.go b/endpoints/publicProxy/authOAuth.go index 293a2ed4..17299120 100644 --- a/endpoints/publicProxy/authOAuth.go +++ b/endpoints/publicProxy/authOAuth.go @@ -58,7 +58,7 @@ func (h *authHandler) handleOAuth(w http.ResponseWriter, r *http.Request, cfg ma return false } - if !h.validateEmailDomain(w, oauthMap, cookie, h.cfg) { + if !h.validateEmailDomain(w, oauthMap, cookie) { return false } @@ -105,7 +105,7 @@ func (h *authHandler) validateOAuthToken(w http.ResponseWriter, r *http.Request, return true } -func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[string]interface{}, cookie *http.Cookie, cfg *Config) bool { +func (h *authHandler) validateEmailDomain(w http.ResponseWriter, oauthCfg map[string]interface{}, cookie *http.Cookie) bool { if patterns, found := oauthCfg["email_domains"].([]interface{}); found && len(patterns) > 0 { tkn, _ := jwt.ParseWithClaims(cookie.Value, &zrokClaims{}, func(t *jwt.Token) (interface{}, error) { return h.signingKey, nil From 5ff6d2578dacdcace3676e40feb062b1e48edc98 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 16:01:45 -0400 Subject: [PATCH 7/9] UnauthorizedData (#1012) --- endpoints/publicProxy/cookies.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/endpoints/publicProxy/cookies.go b/endpoints/publicProxy/cookies.go index b7ed3df1..e8c90782 100644 --- a/endpoints/publicProxy/cookies.go +++ b/endpoints/publicProxy/cookies.go @@ -28,7 +28,7 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { if targetHost == "" { err := errors.New("targetHost claim must not be empty") logrus.Error(err) - proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err)) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(err)) return } targetHost = strings.Split(targetHost, "/")[0] @@ -36,7 +36,7 @@ func setSessionCookie(w http.ResponseWriter, req sessionCookieRequest) { encryptedAccessToken, err := encryptToken(req.accessToken, req.encryptionKey) if err != nil { logrus.Errorf("failed to encrypt access token: %v", err) - proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(errors.New("failed to encrypt access token"))) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(errors.New("failed to encrypt access token"))) return } From 7b2ff044d84546896a85d704709666bbfebc73d6 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 16:03:32 -0400 Subject: [PATCH 8/9] UnauthorizedData (#1012) --- endpoints/publicProxy/providerGithub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/endpoints/publicProxy/providerGithub.go b/endpoints/publicProxy/providerGithub.go index 144a2b44..3792721d 100644 --- a/endpoints/publicProxy/providerGithub.go +++ b/endpoints/publicProxy/providerGithub.go @@ -98,7 +98,7 @@ func (c *githubConfigurer) configure() error { if err != nil { err := fmt.Errorf("unable to unescape targetHost: %v", err) logrus.Error(err) - proxyUi.WriteUnauthorized(w, proxyUi.RequiredData("unauthorized!", "unauthorized!").WithError(err)) + proxyUi.WriteUnauthorized(w, proxyUi.UnauthorizedData().WithError(err)) return } rp.AuthURLHandler(func() string { From fe72fdc6f40cee3efb69797c92c8a7c56574e8d1 Mon Sep 17 00:00:00 2001 From: Michael Quigley Date: Wed, 6 Aug 2025 16:14:22 -0400 Subject: [PATCH 9/9] better responsiveness (#1012) --- endpoints/proxyUi/template.html | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/endpoints/proxyUi/template.html b/endpoints/proxyUi/template.html index 70829561..c39ebaca 100644 --- a/endpoints/proxyUi/template.html +++ b/endpoints/proxyUi/template.html @@ -55,8 +55,13 @@ font-size: 48pt; font-weight: bold; margin: 0; + margin-left: 25px; color: #9bf316; } + #banner svg { + width: 80px; + height: 105px; + } #container { display: flex; align-items: center; @@ -67,13 +72,29 @@ text-align: center; background-color: white; border-radius: 12px; + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; + hyphens: auto; } + @media (max-width: 599px) { + body { + font-size: 0.5em; + } + #banner h1 { + font-size: 24pt; + } + #banner svg { + width: 40px; + height: 52.5px; + } + }