set and check zrok-access JWT audience matches share host

This commit is contained in:
Kenneth Bingham 2023-12-15 12:13:30 -05:00
parent b7f9b8615f
commit ece79f28d9
No known key found for this signature in database
GPG Key ID: 31709281860130B6
4 changed files with 26 additions and 7 deletions

View File

@ -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)
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
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)

View File

@ -165,8 +165,7 @@ func configureGithubOauth(cfg *OauthConfig, tls bool) error {
} else {
authCheckInterval = i
}
SetZrokCookie(w, cfg.CookieDomain, primaryEmail, tokens.AccessToken, "github", authCheckInterval, key)
SetZrokCookie(w, cfg.CookieDomain, primaryEmail, tokens.AccessToken, "github", authCheckInterval, key, token.Claims.(*IntermediateJWT).Host)
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
}

View File

@ -145,8 +145,7 @@ func configureGoogleOauth(cfg *OauthConfig, tls bool) error {
} else {
authCheckInterval = i
}
SetZrokCookie(w, cfg.CookieDomain, rDat.Email, tokens.AccessToken, "google", authCheckInterval, key)
SetZrokCookie(w, cfg.CookieDomain, rDat.Email, tokens.AccessToken, "google", authCheckInterval, key, token.Claims.(*IntermediateJWT).Host)
http.Redirect(w, r, fmt.Sprintf("%s://%s", scheme, token.Claims.(*IntermediateJWT).Host), http.StatusFound)
}

View File

@ -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)
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 castedDomains, ok := validDomains.([]interface{}); !ok {
logrus.Error("invalid email domain format")
@ -313,15 +319,26 @@ type ZrokClaims struct {
Email string `json:"email"`
AccessToken string `json:"accessToken"`
Provider string `json:"provider"`
Audience string `json:"aud"`
AuthorizationCheckInterval time.Duration `json:"authorizationCheckInterval"`
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{
Email: email,
AccessToken: accessToken,
Provider: provider,
Audience: targetHost,
AuthorizationCheckInterval: checkInterval,
})
sTkn, err := tkn.SignedString(key)
@ -334,10 +351,12 @@ func SetZrokCookie(w http.ResponseWriter, domain, email, accessToken, provider s
Name: "zrok-access",
Value: sTkn,
MaxAge: int(checkInterval.Seconds()),
Domain: domain,
Domain: cookieDomain,
Path: "/",
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
})
}