mirror of
https://github.com/openziti/zrok.git
synced 2024-11-24 17:13:51 +01:00
commit
d9d7bc6f5e
@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
CHANGE: OpenZiti SDK updated to `v0.21.2`. All `ziti.ListenOptions` listener options configured to use `WaitForNEstablishedListeners: 1`. When a `zrok share` client or an `sdk.Share` client are connected to an OpenZiti router that supports "listener established" events, then listen calls will not return until the listener is fully established on the OpenZiti network. Previously a `zrok share` client could report that it is fully operational and listening before the listener is fully established on the OpenZiti network; in practice this produced a very small window of time when the share would not be ready to accept requests. This change eliminates this window of time (https://github.com/openziti/zrok/issues/490)
|
CHANGE: OpenZiti SDK updated to `v0.21.2`. All `ziti.ListenOptions` listener options configured to use `WaitForNEstablishedListeners: 1`. When a `zrok share` client or an `sdk.Share` client are connected to an OpenZiti router that supports "listener established" events, then listen calls will not return until the listener is fully established on the OpenZiti network. Previously a `zrok share` client could report that it is fully operational and listening before the listener is fully established on the OpenZiti network; in practice this produced a very small window of time when the share would not be ready to accept requests. This change eliminates this window of time (https://github.com/openziti/zrok/issues/490)
|
||||||
|
|
||||||
|
FIX: Require the JWT in a zrok OAuth cookie to have an audience claim that matches the public share hostname. This prevents a cookie from one share from being use to log in to another share.
|
||||||
|
|
||||||
## v0.4.19
|
## v0.4.19
|
||||||
|
|
||||||
FEATURE: Reserved shares now support unique names ("vanity tokens"). This allows for the creation of reserved shares with identifiable names rather than generated share tokens. Includes basic support for profanity checking (https://github.com/openziti/zrok/issues/401)
|
FEATURE: Reserved shares now support unique names ("vanity tokens"). This allows for the creation of reserved shares with identifiable names rather than generated share tokens. Includes basic support for profanity checking (https://github.com/openziti/zrok/issues/401)
|
||||||
|
@ -87,7 +87,7 @@ func (cmd *testWebsocketCommand) run(_ *cobra.Command, args []string) {
|
|||||||
addr = cmd.serviceName
|
addr = cmd.serviceName
|
||||||
} else {
|
} else {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
logrus.Error("Address required if not using ziti")
|
logrus.Error("address required if not using ziti")
|
||||||
flag.Usage()
|
flag.Usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@ -102,13 +102,13 @@ func (cmd *testWebsocketCommand) run(_ *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
defer c.Close(websocket.StatusInternalError, "the sky is falling")
|
defer c.Close(websocket.StatusInternalError, "the sky is falling")
|
||||||
|
|
||||||
logrus.Info("Writing to server...")
|
logrus.Info("writing to server...")
|
||||||
err = wsjson.Write(ctx, c, "hi")
|
err = wsjson.Write(ctx, c, "hi")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logrus.Info("Reading response...")
|
logrus.Info("reading response...")
|
||||||
typ, dat, err := c.Read(ctx)
|
typ, dat, err := c.Read(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# this builds docker.io/openziti/zrok
|
# this builds docker.io/openziti/zrok
|
||||||
ARG ZITI_CLI_TAG="0.31.0"
|
ARG ZITI_CLI_TAG="0.31.2"
|
||||||
ARG ZITI_CLI_IMAGE="docker.io/openziti/ziti-cli"
|
ARG ZITI_CLI_IMAGE="docker.io/openziti/ziti-cli"
|
||||||
# this builds docker.io/openziti/ziti-controller
|
# this builds docker.io/openziti/ziti-controller
|
||||||
FROM ${ZITI_CLI_IMAGE}:${ZITI_CLI_TAG}
|
FROM ${ZITI_CLI_IMAGE}:${ZITI_CLI_TAG}
|
||||||
|
@ -80,7 +80,7 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
host, err := url.QueryUnescape(r.URL.Query().Get("targethost"))
|
host, err := url.QueryUnescape(r.URL.Query().Get("targethost"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Unable to unescape target host: %v", err)
|
logrus.Errorf("unable to unescape target host: %v", err)
|
||||||
}
|
}
|
||||||
rp.AuthURLHandler(func() string {
|
rp.AuthURLHandler(func() string {
|
||||||
id := uuid.New().String()
|
id := uuid.New().String()
|
||||||
@ -99,7 +99,7 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
})
|
})
|
||||||
s, err := t.SignedString(key)
|
s, err := t.SignedString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Unable to sign intermediate JWT: %v", err)
|
logrus.Errorf("unable to sign intermediate JWT: %v", err)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}, party, rp.WithURLParam("access_type", "offline"))(w, r)
|
}, party, rp.WithURLParam("access_type", "offline"))(w, r)
|
||||||
@ -121,7 +121,7 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokens.AccessToken))
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokens.AccessToken))
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("Error getting user info from github: " + err.Error() + "\n")
|
logrus.Errorf("error getting user info from github: %v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -130,14 +130,14 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
}()
|
}()
|
||||||
response, err := io.ReadAll(resp.Body)
|
response, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Error reading response body: %v", err)
|
logrus.Errorf("error reading response body: %v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var rDat []githubUserResp
|
var rDat []githubUserResp
|
||||||
err = json.Unmarshal(response, &rDat)
|
err = json.Unmarshal(response, &rDat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Error unmarshalling google oauth response: %v", err)
|
logrus.Errorf("error unmarshalling google oauth response: %v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -165,8 +165,7 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
} else {
|
} else {
|
||||||
authCheckInterval = i
|
authCheckInterval = i
|
||||||
}
|
}
|
||||||
|
SetZrokCookie(w, cfg.CookieDomain, primaryEmail, tokens.AccessToken, "github", authCheckInterval, key, token.Claims.(*IntermediateJWT).Host)
|
||||||
SetZrokCookie(w, cfg.CookieDomain, primaryEmail, tokens.AccessToken, "github", authCheckInterval, key)
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
|
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
host, err := url.QueryUnescape(r.URL.Query().Get("targethost"))
|
host, err := url.QueryUnescape(r.URL.Query().Get("targethost"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Unable to unescape target host: %v", err)
|
logrus.Errorf("unable to unescape target host: %v", err)
|
||||||
}
|
}
|
||||||
rp.AuthURLHandler(func() string {
|
rp.AuthURLHandler(func() string {
|
||||||
id := uuid.New().String()
|
id := uuid.New().String()
|
||||||
@ -97,7 +97,7 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
})
|
})
|
||||||
s, err := t.SignedString(key)
|
s, err := t.SignedString(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Unable to sign intermediate JWT: %v", err)
|
logrus.Errorf("unable to sign intermediate JWT: %v", err)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}, party, rp.WithURLParam("access_type", "offline"))(w, r)
|
}, party, rp.WithURLParam("access_type", "offline"))(w, r)
|
||||||
@ -108,7 +108,7 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
getEmail := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty) {
|
getEmail := func(w http.ResponseWriter, r *http.Request, tokens *oidc.Tokens[*oidc.IDTokenClaims], state string, rp rp.RelyingParty) {
|
||||||
resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + url.QueryEscape(tokens.AccessToken))
|
resp, err := http.Get("https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + url.QueryEscape(tokens.AccessToken))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error("Error getting user info from google: " + err.Error() + "\n")
|
logrus.Errorf("error getting user info from google: %v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -117,15 +117,15 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
}()
|
}()
|
||||||
response, err := io.ReadAll(resp.Body)
|
response, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Error reading response body: %v", err)
|
logrus.Errorf("error reading response body: %v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logrus.Infof("Response from google userinfo endpoint: %s", string(response))
|
logrus.Infof("response from google userinfo endpoint: %s", string(response))
|
||||||
rDat := googleOauthEmailResp{}
|
rDat := googleOauthEmailResp{}
|
||||||
err = json.Unmarshal(response, &rDat)
|
err = json.Unmarshal(response, &rDat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("Error unmarshalling google oauth response: %v", err)
|
logrus.Errorf("error unmarshalling google oauth response: %v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -145,8 +145,7 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
|
|||||||
} else {
|
} else {
|
||||||
authCheckInterval = i
|
authCheckInterval = i
|
||||||
}
|
}
|
||||||
|
SetZrokCookie(w, cfg.CookieDomain, rDat.Email, tokens.AccessToken, "google", authCheckInterval, key, token.Claims.(*IntermediateJWT).Host)
|
||||||
SetZrokCookie(w, cfg.CookieDomain, rDat.Email, tokens.AccessToken, "google", authCheckInterval, key)
|
|
||||||
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
|
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,7 +211,7 @@ func authHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Contex
|
|||||||
if provider, found := oauthCfg.(map[string]interface{})["provider"]; found {
|
if provider, found := oauthCfg.(map[string]interface{})["provider"]; found {
|
||||||
var authCheckInterval time.Duration
|
var authCheckInterval time.Duration
|
||||||
if checkInterval, found := oauthCfg.(map[string]interface{})["authorization_check_interval"]; !found {
|
if checkInterval, found := oauthCfg.(map[string]interface{})["authorization_check_interval"]; !found {
|
||||||
logrus.Errorf("Missing authorization check interval in share config. Defaulting to 3 hours")
|
logrus.Errorf("missing authorization check interval in share config. Defaulting to 3 hours")
|
||||||
authCheckInterval = 3 * time.Hour
|
authCheckInterval = 3 * time.Hour
|
||||||
} else {
|
} else {
|
||||||
i, err := time.ParseDuration(checkInterval.(string))
|
i, err := time.ParseDuration(checkInterval.(string))
|
||||||
@ -253,6 +253,12 @@ func authHandler(handler http.Handler, pcfg *Config, key []byte, ctx ziti.Contex
|
|||||||
oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval)
|
oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if claims.Audience != r.Host {
|
||||||
|
logrus.Errorf("audience claim '%s' does not match requested host '%s'; restarting auth flow", claims.Audience, r.Host)
|
||||||
|
oauthLoginRequired(w, r, pcfg.Oauth, provider.(string), target, authCheckInterval)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if validDomains, found := oauthCfg.(map[string]interface{})["email_domains"]; found {
|
if validDomains, found := oauthCfg.(map[string]interface{})["email_domains"]; found {
|
||||||
if castedDomains, ok := validDomains.([]interface{}); !ok {
|
if castedDomains, ok := validDomains.([]interface{}); !ok {
|
||||||
logrus.Error("invalid email domain format")
|
logrus.Error("invalid email domain format")
|
||||||
@ -313,15 +319,26 @@ type ZrokClaims struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
AccessToken string `json:"accessToken"`
|
AccessToken string `json:"accessToken"`
|
||||||
Provider string `json:"provider"`
|
Provider string `json:"provider"`
|
||||||
|
Audience string `json:"aud"`
|
||||||
AuthorizationCheckInterval time.Duration `json:"authorizationCheckInterval"`
|
AuthorizationCheckInterval time.Duration `json:"authorizationCheckInterval"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetZrokCookie(w http.ResponseWriter, domain, email, accessToken, provider string, checkInterval time.Duration, key []byte) {
|
func SetZrokCookie(w http.ResponseWriter, cookieDomain, email, accessToken, provider string, checkInterval time.Duration, key []byte, targetHost string) {
|
||||||
|
targetHost = strings.TrimSpace(targetHost)
|
||||||
|
if targetHost == "" {
|
||||||
|
logrus.Error("host claim must not be empty")
|
||||||
|
http.Error(w, "host claim must not be empty", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetHost = strings.Split(targetHost, "/")[0]
|
||||||
|
logrus.Debugf("setting zrok-access cookie JWT audience '%s'", targetHost)
|
||||||
|
|
||||||
tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, ZrokClaims{
|
tkn := jwt.NewWithClaims(jwt.SigningMethodHS256, ZrokClaims{
|
||||||
Email: email,
|
Email: email,
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
Provider: provider,
|
Provider: provider,
|
||||||
|
Audience: targetHost,
|
||||||
AuthorizationCheckInterval: checkInterval,
|
AuthorizationCheckInterval: checkInterval,
|
||||||
})
|
})
|
||||||
sTkn, err := tkn.SignedString(key)
|
sTkn, err := tkn.SignedString(key)
|
||||||
@ -334,10 +351,12 @@ func SetZrokCookie(w http.ResponseWriter, domain, email, accessToken, provider s
|
|||||||
Name: "zrok-access",
|
Name: "zrok-access",
|
||||||
Value: sTkn,
|
Value: sTkn,
|
||||||
MaxAge: int(checkInterval.Seconds()),
|
MaxAge: int(checkInterval.Seconds()),
|
||||||
Domain: domain,
|
Domain: cookieDomain,
|
||||||
Path: "/",
|
Path: "/",
|
||||||
Expires: time.Now().Add(checkInterval),
|
Expires: time.Now().Add(checkInterval),
|
||||||
//Secure: true, //When tls gets added have this be configured on if tls
|
// 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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user