diff --git a/cmd/zrok/sharePublic.go b/cmd/zrok/sharePublic.go index 5da1e998..a8adaeb2 100644 --- a/cmd/zrok/sharePublic.go +++ b/cmd/zrok/sharePublic.go @@ -111,7 +111,6 @@ func (cmd *sharePublicCommand) run(_ *cobra.Command, args []string) { } if cmd.oauthProvider != "" { - req.Auth = []string{} req.OauthProvider = cmd.oauthProvider req.OauthEmailDomains = cmd.oauthEmailDomains req.OauthAuthorizationCheckInterval = cmd.oauthCheckInterval diff --git a/controller/sharePrivate.go b/controller/sharePrivate.go index 15b05ca8..add11f60 100644 --- a/controller/sharePrivate.go +++ b/controller/sharePrivate.go @@ -18,7 +18,11 @@ func (a *privateResourceAllocator) allocate(envZId, shrToken string, params shar for _, authUser := range params.Body.AuthUsers { authUsers = append(authUsers, &sdk.AuthUser{authUser.Username, authUser.Password}) } - cfgZId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, params.Body.OauthProvider, params.Body.OauthEmailDomains, edge) + cfgZId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, &zrokEdgeSdk.OauthOptions{ + Provider: params.Body.OauthProvider, + EmailDomains: params.Body.OauthEmailDomains, + AuthorizationCheckInterval: params.Body.OauthAuthorizationCheckInterval, + }, edge) if err != nil { return "", nil, err } diff --git a/controller/sharePublic.go b/controller/sharePublic.go index 0e402c5a..e8639fce 100644 --- a/controller/sharePublic.go +++ b/controller/sharePublic.go @@ -18,7 +18,11 @@ func (a *publicResourceAllocator) allocate(envZId, shrToken string, frontendZIds for _, authUser := range params.Body.AuthUsers { authUsers = append(authUsers, &sdk.AuthUser{authUser.Username, authUser.Password}) } - cfgId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, params.Body.OauthProvider, params.Body.OauthEmailDomains, edge) + cfgId, err := zrokEdgeSdk.CreateConfig(zrokProxyConfigId, envZId, shrToken, params.Body.AuthScheme, authUsers, &zrokEdgeSdk.OauthOptions{ + Provider: params.Body.OauthProvider, + EmailDomains: params.Body.OauthEmailDomains, + AuthorizationCheckInterval: params.Body.OauthAuthorizationCheckInterval, + }, edge) if err != nil { return "", nil, err } diff --git a/controller/zrokEdgeSdk/config.go b/controller/zrokEdgeSdk/config.go index 9b9eefae..2c119e96 100644 --- a/controller/zrokEdgeSdk/config.go +++ b/controller/zrokEdgeSdk/config.go @@ -11,7 +11,13 @@ import ( "time" ) -func CreateConfig(cfgTypeZId, envZId, shrToken string, authSchemeStr string, authUsers []*sdk.AuthUser, oauthProvider string, oauthEmailDomains []string, edge *rest_management_api_client.ZitiEdgeManagement) (cfgZId string, err error) { +type OauthOptions struct { + Provider string + EmailDomains []string + AuthorizationCheckInterval string +} + +func CreateConfig(cfgTypeZId, envZId, shrToken string, authSchemeStr string, authUsers []*sdk.AuthUser, oauthOptions *OauthOptions, edge *rest_management_api_client.ZitiEdgeManagement) (cfgZId string, err error) { authScheme, err := sdk.ParseAuthScheme(authSchemeStr) if err != nil { return "", err @@ -25,10 +31,11 @@ func CreateConfig(cfgTypeZId, envZId, shrToken string, authSchemeStr string, aut cfg.BasicAuth.Users = append(cfg.BasicAuth.Users, &sdk.AuthUser{Username: authUser.Username, Password: authUser.Password}) } } - if cfg.AuthScheme == model.Oauth { - cfg.OauthAuth = &model.OauthAuth{ - Provider: oauthProvider, - EmailDomains: oauthEmailDomains, + if cfg.AuthScheme == sdk.Oauth && oauthOptions != nil { + cfg.OauthAuth = &sdk.OauthAuth{ + Provider: oauthOptions.Provider, + EmailDomains: oauthOptions.EmailDomains, + AuthorizationCheckInterval: oauthOptions.AuthorizationCheckInterval, } } cfgCrt := &rest_model.ConfigCreate{ diff --git a/endpoints/publicProxy/github.go b/endpoints/publicProxy/github.go index 39fa295f..a60af83c 100644 --- a/endpoints/publicProxy/github.go +++ b/endpoints/publicProxy/github.go @@ -57,8 +57,9 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error { } type IntermediateJWT struct { - State string `json:"state"` - Share string `json:"share"` + State string `json:"state"` + Share string `json:"share"` + AuthorizationCheckInterval string `json:"authorizationCheckInterval"` jwt.RegisteredClaims } @@ -76,6 +77,7 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error { t := jwt.NewWithClaims(jwt.SigningMethodHS256, IntermediateJWT{ id, r.URL.Query().Get("share"), + r.URL.Query().Get("checkInterval"), jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), @@ -134,8 +136,6 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error { } } - SetZrokCookie(w, primaryEmail, tokens.AccessToken, "github", 3*time.Hour, key) - token, err := jwt.ParseWithClaims(state, &IntermediateJWT{}, func(t *jwt.Token) (interface{}, error) { return key, nil }) @@ -143,6 +143,16 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error { http.Error(w, fmt.Sprintf("After intermediate token parse: %v", err.Error()), http.StatusInternalServerError) return } + + authCheckInterval := 3 * time.Hour + i, err := time.ParseDuration(token.Claims.(*IntermediateJWT).AuthorizationCheckInterval) + if err != nil { + logrus.Errorf("unable to parse authorization check interval: %v. Defaulting to 3 hours", err) + } else { + authCheckInterval = i + } + + SetZrokCookie(w, primaryEmail, tokens.AccessToken, "github", authCheckInterval, key) http.Redirect(w, r, fmt.Sprintf("%s://%s.%s:8080", scheme, token.Claims.(*IntermediateJWT).Share, cfg.RedirectUrl), http.StatusFound) } diff --git a/endpoints/publicProxy/google.go b/endpoints/publicProxy/google.go index ced5f834..4a80c0d9 100644 --- a/endpoints/publicProxy/google.go +++ b/endpoints/publicProxy/google.go @@ -58,8 +58,9 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error { } type IntermediateJWT struct { - State string `json:"state"` - Share string `json:"share"` + State string `json:"state"` + Share string `json:"share"` + AuthorizationCheckInterval string `json:"authorizationCheckInterval"` jwt.RegisteredClaims } @@ -74,6 +75,7 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error { t := jwt.NewWithClaims(jwt.SigningMethodHS256, IntermediateJWT{ id, r.URL.Query().Get("share"), + r.URL.Query().Get("checkInterval"), jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), @@ -113,8 +115,6 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error { return } - SetZrokCookie(w, rDat.Email, tokens.AccessToken, "google", 3*time.Hour, key) - token, err := jwt.ParseWithClaims(state, &IntermediateJWT{}, func(t *jwt.Token) (interface{}, error) { return key, nil }) @@ -122,6 +122,16 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error { http.Error(w, fmt.Sprintf("After intermediate token parse: %v", err.Error()), http.StatusInternalServerError) return } + + authCheckInterval := 3 * time.Hour + i, err := time.ParseDuration(token.Claims.(*IntermediateJWT).AuthorizationCheckInterval) + if err != nil { + logrus.Errorf("unable to parse authorization check interval: %v. Defaulting to 3 hours", err) + } else { + authCheckInterval = i + } + + SetZrokCookie(w, rDat.Email, tokens.AccessToken, "google", authCheckInterval, key) http.Redirect(w, r, fmt.Sprintf("%s://%s.%s:8080", scheme, token.Claims.(*IntermediateJWT).Share, cfg.RedirectUrl), http.StatusFound) } diff --git a/endpoints/publicProxy/http.go b/endpoints/publicProxy/http.go index 7d014ea6..8a2d229e 100644 --- a/endpoints/publicProxy/http.go +++ b/endpoints/publicProxy/http.go @@ -3,32 +3,24 @@ package publicProxy import ( "context" "fmt" - "github.com/openziti/sdk-golang/ziti" - "github.com/openziti/zrok/endpoints" - "github.com/openziti/zrok/endpoints/publicProxy/healthUi" - "github.com/openziti/zrok/endpoints/publicProxy/notFoundUi" - "github.com/openziti/zrok/environment" - "github.com/openziti/zrok/sdk" - "github.com/openziti/zrok/util" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "net" - "net/http" - "net/http/httputil" - "net/url" - "strings" - "time" - "github.com/golang-jwt/jwt/v5" "github.com/openziti/sdk-golang/ziti" "github.com/openziti/zrok/endpoints" "github.com/openziti/zrok/endpoints/publicProxy/healthUi" "github.com/openziti/zrok/endpoints/publicProxy/notFoundUi" "github.com/openziti/zrok/endpoints/publicProxy/unauthorizedUi" + "github.com/openziti/zrok/environment" + "github.com/openziti/zrok/sdk" "github.com/openziti/zrok/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" zhttp "github.com/zitadel/oidc/v2/pkg/http" + "net" + "net/http" + "net/http/httputil" + "net/url" + "strings" + "time" ) type httpFrontend struct { @@ -202,29 +194,6 @@ func authHandler(handler http.Handler, realm string, pcfg *Config, ctx ziti.Cont case string(sdk.Oauth): if oauthCfg, found := cfg["oauth"]; found { if provider, found := oauthCfg.(map[string]interface{})["provider"]; found { - cookie, err := r.Cookie("zrok-access") - if err != nil { - logrus.Errorf("Unable to get access cookie: %v", err) - http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken), http.StatusFound) - return - } - tkn, err := jwt.ParseWithClaims(cookie.Value, &ZrokClaims{}, func(t *jwt.Token) (interface{}, error) { - if pcfg.Oauth == nil { - return nil, fmt.Errorf("Missing oauth configuration for access point. Unable to parse jwt") - } - return pcfg.Oauth.HashKeyRaw, nil - }) - if err != nil { - logrus.Errorf("Unable to parse JWT: %v", err) - http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken), http.StatusFound) - return - } - claims := tkn.Claims.(*ZrokClaims) - if claims.Provider != provider { - logrus.Error("Provider mismatch. Redoing auth flow") - http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken), http.StatusFound) - return - } var authCheckInterval time.Duration if checkInterval, found := oauthCfg.(map[string]interface{})["authorization_check_interval"]; !found { logrus.Errorf("Missing authorization check interval in share config. Defaulting to 3 hours") @@ -238,9 +207,33 @@ func authHandler(handler http.Handler, realm string, pcfg *Config, ctx ziti.Cont authCheckInterval = i } } + + cookie, err := r.Cookie("zrok-access") + if err != nil { + logrus.Errorf("Unable to get access cookie: %v", err) + http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s&checkInterval=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken, authCheckInterval.String()), http.StatusFound) + return + } + tkn, err := jwt.ParseWithClaims(cookie.Value, &ZrokClaims{}, func(t *jwt.Token) (interface{}, error) { + if pcfg.Oauth == nil { + return nil, fmt.Errorf("missing oauth configuration for access point. Unable to parse jwt") + } + return []byte(pcfg.Oauth.HashKeyRaw), nil + }) + if err != nil { + logrus.Errorf("Unable to parse JWT: %v", err) + http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s&checkInterval=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken, authCheckInterval.String()), http.StatusFound) + return + } + claims := tkn.Claims.(*ZrokClaims) + if claims.Provider != provider { + logrus.Error("Provider mismatch. Redoing auth flow") + http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s&checkInterval=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken, authCheckInterval.String()), http.StatusFound) + return + } if claims.AuthorizationCheckInterval != authCheckInterval { logrus.Error("Authorization check interval mismatch. Redoing auth flow") - http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken), http.StatusFound) + http.Redirect(w, r, fmt.Sprintf("http://%s.%s:28080/%s/login?share=%s&checkInterval=%s", shrToken, pcfg.HostMatch, provider.(string), shrToken, authCheckInterval.String()), http.StatusFound) return } if validDomains, found := oauthCfg.(map[string]interface{})["email_domains"]; found { @@ -337,7 +330,7 @@ func SetZrokCookie(w http.ResponseWriter, email, accessToken, provider string, c http.SetCookie(w, &http.Cookie{ Name: "zrok-access", Value: sTkn, - MaxAge: 3000, + MaxAge: int(checkInterval.Seconds()), Domain: "localzrok.io", Path: "/", Expires: time.Now().Add(checkInterval), diff --git a/sdk/config.go b/sdk/config.go index 11145a97..7bcbc1d1 100644 --- a/sdk/config.go +++ b/sdk/config.go @@ -20,8 +20,9 @@ type AuthUser struct { } type OauthAuth struct { - Provider string `json:"provider"` - EmailDomains []string `json:"email_domains"` + Provider string `json:"provider"` + EmailDomains []string `json:"email_domains"` + AuthorizationCheckInterval string `json:"authorization_check_interval"` } func ParseAuthScheme(authScheme string) (AuthScheme, error) { diff --git a/sdk/share.go b/sdk/share.go index dccf8eb6..7f488b10 100644 --- a/sdk/share.go +++ b/sdk/share.go @@ -38,6 +38,10 @@ func CreateShare(root env_core.Root, request *ShareRequest) (*Share, error) { } } + if request.OauthProvider != "" { + out.Body.AuthScheme = string(Oauth) + } + zrok, err := root.Client() if err != nil { return nil, errors.Wrap(err, "error getting zrok client") @@ -70,12 +74,15 @@ func newPrivateShare(root env_core.Root, request *ShareRequest) *share.SharePara func newPublicShare(root env_core.Root, request *ShareRequest) *share.ShareParams { req := share.NewShareParams() req.Body = &rest_model_zrok.ShareRequest{ - EnvZID: root.Environment().ZitiIdentity, - ShareMode: string(request.ShareMode), - FrontendSelection: request.Frontends, - BackendMode: string(request.BackendMode), - BackendProxyEndpoint: request.Target, - AuthScheme: string(None), + EnvZID: root.Environment().ZitiIdentity, + ShareMode: string(request.ShareMode), + FrontendSelection: request.Frontends, + BackendMode: string(request.BackendMode), + BackendProxyEndpoint: request.Target, + AuthScheme: string(None), + OauthEmailDomains: request.OauthEmailDomains, + OauthProvider: request.OauthProvider, + OauthAuthorizationCheckInterval: request.OauthAuthorizationCheckInterval.String(), } return req }