mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-19 14:34:27 +00:00
Supervisor authorize endpoint reuses existing CSRF cookies and signs new ones
- To better support having multiple downstream providers configured, the authorize endpoint will share a CSRF cookie between all downstream providers' authorize endpoints. The first time a user's browser hits the authorize endpoint of any downstream provider, that endpoint will set the cookie. Then if the user starts an authorize flow with that same downstream provider or with any other downstream provider which shares the same domain name (i.e. differentiated by issuer path), then the same cookie will be submitted and respected. - Just in case we are sharing the domain name with some other app, we sign the value of any new CSRF cookie and check the signature when we receive the cookie. This wasn't strictly necessary since we probably won't share a domain name with other apps, but it wasn't hard to add this cookie signing. Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
committed by
Ryan Richard
parent
d73fdb1d33
commit
080bb594b2
@@ -9,6 +9,8 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/ory/fosite"
|
||||
"github.com/ory/fosite/handler/openid"
|
||||
"github.com/ory/fosite/token/jwt"
|
||||
@@ -35,6 +37,9 @@ const (
|
||||
// The name of the browser cookie which shall hold our CSRF value.
|
||||
// `__Host` prefix has a special meaning. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#Cookie_prefixes
|
||||
csrfCookieName = "__Host-pinniped-csrf"
|
||||
|
||||
// The `name` passed to the encoder for encoding and decoding the CSRF cookie contents.
|
||||
csrfCookieEncodingName = "csrf"
|
||||
)
|
||||
|
||||
type IDPListGetter interface {
|
||||
@@ -53,7 +58,8 @@ func NewHandler(
|
||||
generateCSRF func() (csrftoken.CSRFToken, error),
|
||||
generatePKCE func() (pkce.Code, error),
|
||||
generateNonce func() (nonce.Nonce, error),
|
||||
encoder Encoder,
|
||||
upstreamStateEncoder Encoder,
|
||||
cookieCodec securecookie.Codec,
|
||||
) http.Handler {
|
||||
return httperr.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error {
|
||||
if r.Method != http.MethodPost && r.Method != http.MethodGet {
|
||||
@@ -63,6 +69,12 @@ func NewHandler(
|
||||
return httperr.Newf(http.StatusMethodNotAllowed, "%s (try GET or POST)", r.Method)
|
||||
}
|
||||
|
||||
csrfFromCookie, err := readCSRFCookie(r, cookieCodec)
|
||||
if err != nil {
|
||||
plog.InfoErr("error reading CSRF cookie", err)
|
||||
return err
|
||||
}
|
||||
|
||||
authorizeRequester, err := oauthHelper.NewAuthorizeRequest(r.Context(), r)
|
||||
if err != nil {
|
||||
plog.Info("authorize request error", fositeErrorForLog(err)...)
|
||||
@@ -77,11 +89,7 @@ func NewHandler(
|
||||
}
|
||||
|
||||
// Grant the openid scope (for now) if they asked for it so that `NewAuthorizeResponse` will perform its OIDC validations.
|
||||
for _, scope := range authorizeRequester.GetRequestedScopes() {
|
||||
if scope == "openid" {
|
||||
authorizeRequester.GrantScope(scope)
|
||||
}
|
||||
}
|
||||
grantOpenIDScopeIfRequested(authorizeRequester)
|
||||
|
||||
now := time.Now()
|
||||
_, err = oauthHelper.NewAuthorizeResponse(r.Context(), authorizeRequester, &openid.DefaultSession{
|
||||
@@ -103,6 +111,9 @@ func NewHandler(
|
||||
plog.Error("authorize generate error", err)
|
||||
return err
|
||||
}
|
||||
if csrfFromCookie != "" {
|
||||
csrfValue = csrfFromCookie
|
||||
}
|
||||
|
||||
upstreamOAuthConfig := oauth2.Config{
|
||||
ClientID: upstreamIDP.ClientID,
|
||||
@@ -113,13 +124,20 @@ func NewHandler(
|
||||
Scopes: upstreamIDP.Scopes,
|
||||
}
|
||||
|
||||
encodedStateParamValue, err := upstreamStateParam(authorizeRequester, nonceValue, csrfValue, pkceValue, encoder)
|
||||
encodedStateParamValue, err := upstreamStateParam(authorizeRequester, nonceValue, csrfValue, pkceValue, upstreamStateEncoder)
|
||||
if err != nil {
|
||||
plog.Error("authorize upstream state param error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
addCSRFSetCookieHeader(w, csrfValue)
|
||||
if csrfFromCookie == "" {
|
||||
// We did not receive an incoming CSRF cookie, so write a new one.
|
||||
err := addCSRFSetCookieHeader(w, csrfValue, cookieCodec)
|
||||
if err != nil {
|
||||
plog.Error("error setting CSRF cookie", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(w, r,
|
||||
upstreamOAuthConfig.AuthCodeURL(
|
||||
@@ -136,6 +154,30 @@ func NewHandler(
|
||||
})
|
||||
}
|
||||
|
||||
func readCSRFCookie(r *http.Request, codec securecookie.Codec) (csrftoken.CSRFToken, error) {
|
||||
receivedCSRFCookie, err := r.Cookie(csrfCookieName)
|
||||
if err != nil {
|
||||
// Error means that the cookie was not found
|
||||
return "", nil
|
||||
}
|
||||
|
||||
var csrfFromCookie csrftoken.CSRFToken
|
||||
err = codec.Decode(csrfCookieEncodingName, receivedCSRFCookie.Value, &csrfFromCookie)
|
||||
if err != nil {
|
||||
return "", httperr.Wrap(http.StatusUnprocessableEntity, "error reading CSRF cookie", err)
|
||||
}
|
||||
|
||||
return csrfFromCookie, nil
|
||||
}
|
||||
|
||||
func grantOpenIDScopeIfRequested(authorizeRequester fosite.AuthorizeRequester) {
|
||||
for _, scope := range authorizeRequester.GetRequestedScopes() {
|
||||
if scope == "openid" {
|
||||
authorizeRequester.GrantScope(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func chooseUpstreamIDP(idpListGetter IDPListGetter) (*provider.UpstreamOIDCIdentityProvider, error) {
|
||||
allUpstreamIDPs := idpListGetter.GetIDPList()
|
||||
if len(allUpstreamIDPs) == 0 {
|
||||
@@ -202,14 +244,21 @@ func upstreamStateParam(
|
||||
return encodedStateParamValue, nil
|
||||
}
|
||||
|
||||
func addCSRFSetCookieHeader(w http.ResponseWriter, csrfValue csrftoken.CSRFToken) {
|
||||
func addCSRFSetCookieHeader(w http.ResponseWriter, csrfValue csrftoken.CSRFToken, codec securecookie.Codec) error {
|
||||
encodedCSRFValue, err := codec.Encode(csrfCookieEncodingName, csrfValue)
|
||||
if err != nil {
|
||||
return httperr.Wrap(http.StatusInternalServerError, "error encoding CSRF cookie", err)
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: csrfCookieName,
|
||||
Value: string(csrfValue),
|
||||
Value: encodedCSRFValue,
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
Secure: true,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fositeErrorForLog(err error) []interface{} {
|
||||
|
||||
Reference in New Issue
Block a user