pomerium/proxy/state.go
Kenneth Jenkins 5ccd7a520a
support both stateful and stateless authenticate (#4765)
Update the initialization logic for the authenticate, authorize, and
proxy services to automatically select between the stateful
authentication flow and the stateless authentication flow, depending on
whether Pomerium is configured to use the hosted authenticate service.

Add a unit test case to verify that the sign_out handler does not 
trigger a sign in redirect.
2023-12-07 14:24:13 -08:00

128 lines
3.5 KiB
Go

package proxy
import (
"context"
"crypto/cipher"
"net/http"
"net/url"
"github.com/pomerium/pomerium/config"
"github.com/pomerium/pomerium/internal/authenticateflow"
"github.com/pomerium/pomerium/internal/encoding"
"github.com/pomerium/pomerium/internal/encoding/jws"
"github.com/pomerium/pomerium/internal/sessions"
"github.com/pomerium/pomerium/internal/sessions/cookie"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc"
"github.com/pomerium/pomerium/pkg/grpc/databroker"
)
var outboundGRPCConnection = new(grpc.CachedOutboundGRPClientConn)
type authenticateFlow interface {
AuthenticateSignInURL(ctx context.Context, queryParams url.Values, redirectURL *url.URL, idpID string) (string, error)
Callback(w http.ResponseWriter, r *http.Request) error
}
type proxyState struct {
sharedKey []byte
sharedCipher cipher.AEAD
authenticateURL *url.URL
authenticateDashboardURL *url.URL
authenticateSigninURL *url.URL
authenticateRefreshURL *url.URL
encoder encoding.MarshalUnmarshaler
cookieSecret []byte
sessionStore sessions.SessionStore
jwtClaimHeaders config.JWTClaimHeaders
dataBrokerClient databroker.DataBrokerServiceClient
programmaticRedirectDomainWhitelist []string
authenticateFlow authenticateFlow
}
func newProxyStateFromConfig(cfg *config.Config) (*proxyState, error) {
err := ValidateOptions(cfg.Options)
if err != nil {
return nil, err
}
state := new(proxyState)
state.sharedKey, err = cfg.Options.GetSharedKey()
if err != nil {
return nil, err
}
state.sharedCipher, err = cryptutil.NewAEADCipher(state.sharedKey)
if err != nil {
return nil, err
}
state.cookieSecret, err = cfg.Options.GetCookieSecret()
if err != nil {
return nil, err
}
// used to load and verify JWT tokens signed by the authenticate service
state.encoder, err = jws.NewHS256Signer(state.sharedKey)
if err != nil {
return nil, err
}
state.jwtClaimHeaders = cfg.Options.JWTClaimsHeaders
// errors checked in ValidateOptions
state.authenticateURL, err = cfg.Options.GetAuthenticateURL()
if err != nil {
return nil, err
}
state.authenticateDashboardURL = state.authenticateURL.ResolveReference(&url.URL{Path: "/.pomerium/"})
state.authenticateSigninURL = state.authenticateURL.ResolveReference(&url.URL{Path: signinURL})
state.authenticateRefreshURL = state.authenticateURL.ResolveReference(&url.URL{Path: refreshURL})
state.sessionStore, err = cookie.NewStore(func() cookie.Options {
return cookie.Options{
Name: cfg.Options.CookieName,
Domain: cfg.Options.CookieDomain,
Secure: cfg.Options.CookieSecure,
HTTPOnly: cfg.Options.CookieHTTPOnly,
Expire: cfg.Options.CookieExpire,
SameSite: cfg.Options.GetCookieSameSite(),
}
}, state.encoder)
if err != nil {
return nil, err
}
dataBrokerConn, err := outboundGRPCConnection.Get(context.Background(), &grpc.OutboundOptions{
OutboundPort: cfg.OutboundPort,
InstallationID: cfg.Options.InstallationID,
ServiceName: cfg.Options.Services,
SignedJWTKey: state.sharedKey,
})
if err != nil {
return nil, err
}
state.dataBrokerClient = databroker.NewDataBrokerServiceClient(dataBrokerConn)
state.programmaticRedirectDomainWhitelist = cfg.Options.ProgrammaticRedirectDomainWhitelist
if cfg.Options.UseStatelessAuthenticateFlow() {
state.authenticateFlow, err = authenticateflow.NewStateless(
cfg, state.sessionStore, nil, nil, nil)
} else {
state.authenticateFlow, err = authenticateflow.NewStateful(cfg, state.sessionStore)
}
if err != nil {
return nil, err
}
return state, nil
}